C++第一部分介紹基礎:c++:-0,本節介紹C++中函式使用。
函式
函式呼叫
呼叫函式需要先宣告函式原型
巢狀呼叫:
引數傳遞
- 在函式被呼叫時才分配形參的儲存單元
- 實參可以是常量、變數或表示式
- 實參型別必須與形參相符
- 值傳遞是傳遞引數值,即單向傳遞
- 引用傳遞可以實現雙向傳遞
- 常引用作引數可以保障實引數據的安全
傳引用比傳物件計算消耗小
可變引數
C++中提供了兩種方法:
- 如果所有的實參型別相同,可以傳遞一個名為initializer_list的標準庫型別;
- 如果實參的型別不同,我們可以編寫可變引數的模板。
initializer_list
initializer_list是一種標準庫型別,用於表示某種特定型別的值的陣列,該型別定義在同名的標頭檔案中
使用:
- 使用模板時,我們需要在模板名字後面跟一對尖括號,括號內給出型別引數。例如:
initializer_listls; // initializer_list的元素型別是string
initializer_listli; // initializer_list的元素型別是int - initializer_list比較特殊的一點是,其物件中的元素永遠是常量值,我們無法改變initializer_list物件中元素的值。
- 含有initializer_list形參的函式也可以同時擁有其他形參
應用:
- 在編寫程式碼輸出程式產生的錯誤資訊時,最好統一用一個函式實現該功能,使得對所有錯誤的處理能夠整齊劃一。然而錯誤資訊的種類不同,呼叫錯誤資訊輸出函式時傳遞的引數也會各不相同。
- 使用initializer_list編寫一個錯誤資訊輸出函式,使其可以作用於可變數量的形參。
行內函數
作用:編譯時在呼叫處用函式體進行替換,節省了引數傳遞、控制轉移等開銷。
宣告時使用關鍵字: inline
注意:
- 行內函數體內不能有迴圈語句和switch語句;
- 行內函數的定義必須出現在行內函數第一次被呼叫之前;
- 對行內函數不能進行異常介面宣告。
常量表示式
C++-11中新增,用於初始化常量表示式。
(1)constexpr函式語法規定
- constexpr修飾的函式在其所有引數都是constexpr時,一定返回constexpr;
- 函式體中必須有且僅有一條return語句。
(2)constexpr函式舉例
constexpr int get_size() { return 20; }
constexpr int foo = get_size(); //正確:foo是一個常量表示式
函式預設引數值
(1)預設引數值的說明次序
- 有預設引數的形參必須列在形參列表的最右,即預設引數值的右面不能有無預設值的引數;
- 呼叫時實參與形參的結合次序是從左向右。
例:
int add(int x, int y = 5, int z = 6);//正確
int add(int x = 1, int y = 5, int z);//錯誤
int add(int x = 1, int y, int z = 6);//錯誤
(2)預設引數值與函式的呼叫位置 - 如果一個函式有原型宣告,且原型宣告在定義之前,則預設引數值應在函式原型宣告中給出;如果只有函式的定義,或函式定義在前,則預設引數值可以函式定義中給出。
例:
過載函式
函式名相同,引數型別和返回型別不同;引數個數不同
系統函式
C++的系統庫中提供了幾百個函式可供程式設計師使用,例如:
- 求平方根函式(sqrt)
- 求絕對值函式(abs)
- 正弦值、餘弦值和正切值的函式:sin()、cos()、tan()
使用系統函式時要包含相應的標頭檔案,例如:cmath
舉例
計算n次方
計算x的n次方
#include <iostream>
using namespace std;
//計算x的n次方
double power(double x, int n) {
double val = 1.0;
while (n--) val *= x;
return val;
}
int main() {
cout << "5 to the power 2 is "
<< power(5, 2) << endl;
return 0;
}
進位制轉換(8-10)
輸入一個8位二進位制數,將其轉換為十進位制數輸出。
#include <iostream>
using namespace std;
double power (double x, int n); //計算x的n次方
int main() {
int value = 0;
cout << "Enter an 8 bit binary number ";
//cin:只有在輸入完資料再按Enter鍵後,該行資料才被送入鍵盤緩衝區,形成輸入流,提取運算子“>>”才能從中提取資料。需要注意保證從流中讀取資料能正常進行。
for (int i = 7; i >= 0; i--) {
char ch;
cin >> ch;
if (ch == '1')
value += static_cast<int>(power(2, i));//tatic_cast()強制型別轉換
}
cout << "Decimal value is " << value << endl;
return 0;
}
double power (double x, int n) {
double val = 1.0;
while (n--)
val *= x;
return val;
}
計算π
π的計算公式如下:
#include <iostream>
using namespace std;
//求arctan
double arctan(double x) {
double sqr = x * x;
double e = x;
double r = 0;
int i = 1;
while (e / i > 1e-15) {
double f = e / i;
r = (i % 4 == 1) ? r + f : r - f;
e = e * sqr;
i += 2;
}
return r;
}
int main() {
double a = 16.0 * arctan(1/5.0);
double b = 4.0 * arctan(1/239.0);
//注意:因為整數相除結果取整,如果引數寫1/5,1/239,結果就都是0
cout << "PI = " << a - b << endl;
return 0;
}
尋找回文數
尋找並輸出11~999之間的數M,它滿足\(M、M^2和M^3\)均為迴文數。
迴文:各位數字左右對稱的整數。
例如:11滿足上述條件:112=121,113=1331。
分析:
用除以10取餘的方法,從最低位開始,依次取出該數的各位數字。按反序重新構成新的數,比較與原數是否相等,若相等,則原數為迴文。
#include <iostream>
using namespace std;
//判斷n是否為迴文數
bool symm(unsigned n) {
unsigned i = n;
unsigned m = 0;
while (i > 0) {
m = m * 10 + i % 10;
i /= 10;
}
return m == n;
}
int main() {
for(unsigned m = 11; m < 1000; m++)
if (symm(m) && symm(m * m) && symm(m * m * m)) {
cout << "m = " << m;
cout << " m * m = " << m * m;
cout << " m * m * m = "
<< m * m * m << endl;
}
return 0;
}
分段函式
計算分段函式,並輸出結果
分析:
計算\(sin(x)\)的公式,精度為\(10^{-10}\):
#include "iostream"
#include "cmath"
using namespace std;
const double T=1e-10; //定義計算精度10^{-10}
//計算sin(x)
double tsin(double x)
{
double g=0;
double t=x;
int n=1;
do{
g+=t;
n++;
t=-t*x*x/(2*n-1)/(2*n-2);
}while(fabs(t)>=T);//fabs:絕對值
return g;
}
int main()
{
double k,r,s;
cout << "r=";
cin >>r;
cout << "s=";
cin >>s;
if(r*r<=s*s)
{
k= sqrt(tsin(r)*tsin(r)+tsin(s)*tsin(s));
}else
k=tsin(r*s)/2;
cout <<k <<endl;
return 0;
}
擲骰子
每個骰子有六面,點數分別為1、2、3、4、5、6。遊戲者在程式開始時輸入一個無符號整數,作為產生隨機數的種子。每輪投兩次骰子,第一輪如果和數為7或11則為勝,遊戲結束;和數為2、3或12則為負,遊戲結束;和數為其它值則將此值作為自己的點數,繼續第二輪、第三輪...直到某輪的和數等於點數則取勝,若在此前出現和數為7則為負。
分析:
(1)rand函式
函式原型:int rand(void);
所需標頭檔案:
功能和返回值:求出並返回一個偽隨機數
(2)srand函式
void srand(unsigned int seed);
引數:seed產生隨機數的種子
所需標頭檔案:
功能:為使rand()產生一序列偽隨機整數而設定起始點。使用1作為seed引數,可以重新初化rand()。
#include "iostream"
#include "cmath"
using namespace std;
enum GameStatus{Win,Lose,Playing};//列舉儲存狀態
//擲骰子,計算和數,輸出和數
int rollDice()
{
int die1=1+rand()%6;
int die2=1+rand()%6;
int sum=die1+die2;
cout << die1<<"+"<<die2<<"="<<sum<<endl;
return sum;
}
int main()
{
int sum,myPoint;
GameStatus status;
unsigned seed;
int rollDice();
cout <<"請輸入種子:";
cin >> seed;
srand(seed);//將種子傳給rand()
sum=rollDice();//第一輪擲骰子
switch (sum) {
case 7:
case 11:
status=Win;
break;
case 2:
case 3:
case 12:
status=Lose;
break;
default:
status=Playing;
myPoint=sum;
cout << "點數為"<<myPoint<<endl;
break;
}
//第二輪以後
while (status==Playing)
{
sum=rollDice();
if(sum==myPoint) //某輪和數等於點數取勝
{
status=Win;
}else if(sum ==7)//出現和數為7則負
{
status=Lose;
}
}
//輸出
if(status==Win)
{
cout << "Win"<<endl;
} else
cout << "Lose" <<endl;
return 0;
}
疑問:為什麼die1和die2是一樣的?
種子一樣!
srand(2);
int die1=1+rand()%6;
srand(1);
int die2=1+rand()%6;
cout<<die1<<","<<die2<<endl;
求組合數
#include <iostream>
using namespace std;
int commit(int n,int k)
{
if(k>n)
return 0;
else if(n==k || k==0)
return 1;
else
return commit(n-1,k)+ commit(n-1,k-1);
}
int main() {
int n,k;
cout << "請輸入n和k:";
cin >> n>>k;
cout <<"C("<<n<<","<<k<<")="<< commit(n,k)<<endl;
return 0;
}
漢諾塔問題
有三根針A、B、C。A針上有N個盤子,大的在下,小的在上,要求把這N個盤子從A針移到C針,在移動過程中可以藉助B針,每次只允許移動一個盤,且在移動過程中在三根針上都保持大盤在下,小盤在上。
分析:
將n 個盤子從A針移到C針可以分解為三個步驟:
- 將A 上n-1個盤子移到 B針上(藉助C針);
- 把A針上剩下的一個盤子移到C針上;
- 將n-1個盤子從B針移到C針上(藉助A針)。
#include <iostream>
using namespace std;
//將src針的最上面一個盤子移動到dest針上
void move(char src, char dest) {
cout << src << " --> " << dest << endl;
}
//將n個盤子從src針移動到dest針,以medium針作為中轉
void hanoi(int n, char src, char medium, char dest)
{
if (n == 1)
move(src, dest);
else {
//將A 上n-1個盤子移到 B針上(藉助C針);
hanoi(n - 1, src, dest, medium);
//把A針上剩下的一個盤子移到C針上;
move(src, dest);
//將n-1個盤子從B針移到C針上(藉助A針)
hanoi(n - 1, medium, src, dest);
}
}
int main() {
int m;
cout << "Enter the number of diskes: ";
cin >> m;
cout << "the steps to moving " << m << " diskes:" << endl;
hanoi(m,'A','B','C');
return 0;
}
值交換
輸入兩個整數並交換
分析:
引數傳遞有兩種:值傳遞和引用傳遞
引用就是別名,定義int &a=i;
值傳遞,並沒有交換
#include<iostream>
using namespace std;
void swap(int a, int b) {
int t = a;
a = b;
b = t;
}
int main() {
int x = 5, y = 10;
cout<<"x = "<<x<<" y = "<<y<<endl;
swap(x, y);
cout<<"x = "<<x<<" y = "<<y<<endl;
return 0;
}
執行結果:
x = 5 y = 10
x = 5 y = 10
引用傳遞:
#include<iostream>
using namespace std;
//a和b分別是x和y的引用
void swap(int& a, int& b) {
int t = a;
a = b;
b = t;
}
int main() {
int x = 5, y = 10;
cout<<"x = "<<x<<" y = "<<y<<endl;
swap(x, y);
cout<<"x = "<<x<<" y = "<<y<< endl;
return 0;
}
x = 5 y = 10
x = 10 y = 5
行內函數
#include <iostream>
using namespace std;
const double PI = 3.14159265358979;
inline double calArea(double radius) {
return PI * radius * radius;
}
int main() {
double r = 3.0;
double area = calArea(r);
cout << area << endl;
return 0;
}
計算長方體的體積
有三個形參:length(長)、width(寬)、height(高),其中width和height帶有預設值2和3。
#include <iostream>
#include <iomanip>
using namespace std;
int getVolume(int length, int width = 2, int height = 3);//宣告在前,定義預設引數值
int main() {
const int X = 10, Y = 12, Z = 15;
cout << "Some box data is " ;
cout << getVolume(X, Y, Z) << endl;
cout << "Some box data is " ;
cout << getVolume(X, Y) << endl;
cout << "Some box data is " ;
cout << getVolume(X) << endl;
return 0;
}
//定義中不定義預設引數值
int getVolume(int length, int width, int height) {
cout << setw(5) << length << setw(5) << width << setw(5)
<< height << '\t';//setw用於設定欄位的寬度
return length * width * height;
}
過載函式
編寫兩個名為sumOfSquare的過載函式,分別求兩整數的平方和及兩實數的平方和。
#include <iostream>
using namespace std;
//型別不同
int sumOfSquare(int a, int b) {
return a * a + b * b;
}
double sumOfSquare(double a, double b) {
return a * a + b * b;
}
int main() {
int m, n;
cout << "Enter two integer: ";
cin >> m >> n;
cout<<"Their sum of square: "<<sumOfSquare(m, n)<<endl;
double x, y;
cout << "Enter two real number: ";
cin >> x >> y;
cout<<"Their sum of square: "<<sumOfSquare(x, y)<<endl;
return 0;
}
求正弦值、餘弦值和正切值
從鍵盤輸入一個角度值,求出該角度的正弦值、餘弦值和正切值。
#include <iostream>
#include <cmath>
using namespace std;
const double PI = 3.14159265358979;
int main() {
double angle;
cout << "Please enter an angle: ";
cin >> angle; //輸入角度值
double radian = angle * PI / 180; //轉為弧度
cout << "sin(" << angle << ") = " << sin(radian) <<endl;
cout << "cos(" << angle << ") = " << cos(radian) <<endl;
cout << "tan(" << angle << ") = " << tan(radian) <<endl;
return 0;
}
習題
(1)已知函式FA呼叫FB,若要把這兩個函式定義在同一個檔案中,則
- FA必須定義在FB之前
- FB必須定義在FA之前
- 若FA定義在FB之後,則FA的原型必須出現在FB的定義之前
- 若FB定義在FA之後,則FB的原型必須出現在FA的定義之前(對)
函式原型,就是函式的宣告
(2)在()時為形參分配儲存空間。
- 函式宣告
- 函式定義
- 函式呼叫(對)
(3)可以定義指向引用的指標.
錯。因為引用不是物件,引用並沒有在程式中佔據記憶體空間,故沒有地址的說法.
(4)類內實現好的成員函式是行內函數,在類體外實現的函式不能是行內函數
錯。因為行內函數主要的作用是在某些情況(某個函式被呼叫多次)下可以提高程式的執行效率。定義行內函數,可以顯式用inline宣告,也可以直接在類內定義好實現. 擴充套件閱讀
(5)已知程式中有以下宣告:
- int nonconst_var = 100;
- const int const_var1 = 2;
- const int const_var2 = nonconst_var;
則下述程式碼中正確的是: - constexpr int constexpr_var1 = 3 + const_var1 * 4; (對)
- constexpr int constexpr_var2 = 3 + nonconst_var * 4;
- constexpr int constexpr_var3 = 3 + const_var2 * 4;
分析:
constexpr的變數的值必須是編譯器在編譯的時候就可以確定的。上例中因為nonconst_var的值在語法上來講,執行期間可能被更改,所以編譯期間無法確定,不屬於常數表示式。因為const_var2是由非常數表示式來初始化的,所以const_var2也不是常數表示式。但const_var2本身的宣告,定義及初始化是合法的。constexpr比const更嚴格,用來初始化constexpr_var2和constexpr_var3的也都不是常數表示式,所以他們的定義都是錯誤的。
(6)例3-15中的getVolume函式,如果直接呼叫int a=getVolume();後,會有什麼樣的結果?
- 編譯執行正確,a的值為0
- 編譯執行正確,a的值為6
- 編譯報錯(對)
- 執行出錯
分析:
函式有一個形參沒有預設值,所以至少要提供一個實參。注意,預設不是0
(7)判斷兩個浮點數是否相等
abs(a-b)<1e-10 //abs求絕對值