C++知識點摘錄四

2020-08-11 17:47:29

函數

函數基礎

1.區域性物件:
形參和函數內部定義的變數統稱爲區域性變數。
2.自動物件:
我們把只存在於塊執行期間的物件稱爲自動物件。當塊的執行結束後,塊中建立的自動物件的值就變成爲定義的了。
3.區域性靜態物件:
在程式的執行路徑第一次經過物件定義語句時初始化,並且直到程式終止才被銷燬,再次期間 即使物件所在的函數結束執行也不會對它有影響。

size_t count_calls()
{
	static size_t ctr = 0;
	return ++ctr;
}
int main()
{
	for(size_t i = 0;i! = 10;++i)
		cout<<count_calls()<<endl;
		return 0;
}

參數傳遞

指針的行爲和其他非參照型別一樣。當執行指針拷貝操作時,拷貝的是指針的值。拷貝之後,兩個指針是不同的指針。因爲指針使我們可以間接地存取它所指的物件,所以通過指針可以修改它所指物件的值。

int n = 0,i = 42;
int *p = &n,*q = &i;//p指向n,q指向i
*p = 42;//n的值改變,p不變
p = q;//p現在指向了i,但是i和n的值不變

void reset(int *ip)
{
	*ip = 0;
	ip = 0;
}
//呼叫reset函數之後,實參所指的物件被置爲0,但是實參本身並沒有改變。
int i  = 42;
reset(&i);
cout<<"i = "<<i<<endl;

熟悉c的程式設計師常常使用指針型別的形參訪問函數外部的物件。c++中,建議使用參照型別的形參替代指針。

string::size_type find_char(const string &s,char c ,string::size_type &occurs)
{
	auto ret = s.size();
	occurs = 0;
	for(decltype(ret) i = 0;i!=s.size();++i)
	{
		if(s[i] == c){
			if(ret == s.size()){
				ret = i;
			}
			++occurs;
		}
	}
	return ret;
}

auto index = find_char(s,'0',ctr);

行參的初始化方式和變數的初始化方式是一樣的。我們可以使用非常數 初始化一個底層const物件,但是反過來卻不行。同時一個普通的參照必須是同類型的物件初始化。

int i= 42;
const int *cp  = &i;//正確,但是cp不能改變i
const int &r  = i;//正確但是r不能改變i
const int &r2 = 42;//正確

把函數不會改變的形式定義成參照是一種比較常見的錯誤,這麼做帶給函數的呼叫者是一種誤導,即函數可以修改它的實參的值。此外,使用參照而非常數參照也會極大的限制函數所能接受的實參型別。我們不能把const物件,字面值或者需要型別轉換的物件傳遞給普通的參照實參。

陣列是以指針的形式傳遞給函數的。這裏列舉2種管理指針行參的常用技術:

1.使用標準庫規範

void print(const int*beg,const int *end)
{
    while (beg!=end) {
        cout<<*beg++<<endl;//輸出當前值並將指針移動
    }
}
 int j[2] = {0,1};
    print(std::begin(j), std::end(j));

2.顯示傳遞一個表示陣列大小的形參

//const int ia[] 等價於 const int *ia
void print(const int ia[],size_t size)
{
	for(size_t i = 0;i!=size;++i){
		cout << ia[i] << endl;
	}
}

int j[] = {0,1};
print(j,end(j) - begin(j));

當函數不需要對陣列元素執行寫操作的時候,陣列行參應該指向const指針。只有當函數確實要改變元素值的時候,才把形參定義成非常數指針。

返回型別和return語句

無返回值函數:
沒有返回值的return語句只能在返回型別是void的函數中。返回void的函數不要求非得有return語句,因爲這類函數的最後一句後面會隱式地執行return。

有返回值函數:
只要函數的返回型別不是void,則該函數內的每條return語句必須返回一個值。

值是如何被返回的?
返回的值用於初始化呼叫點的一個臨時量,該臨時量就是函數呼叫的結果。
函數完成後,它所佔用的儲存空間也隨之釋放掉,因此,不要返回區域性物件的參照或指針。

const string &manip()
{
	string ret;
	if(!ret.empty()){
		return ret;//錯誤,返回區域性物件的參照
	}else{
		return "Empty";//錯誤,「empty」是一個區域性臨時量
	}
}

返回陣列指針
因爲陣列不能被拷貝,所以函數不能返回陣列。不過,函數可以返回陣列的指針或參照。最直接的方法就是使用型別別名。

typedef int arrT[10];//arrt是一個型別別名,它表示的型別是含有10個整數的陣列。
using arrT = int[10];//和上面是等價宣告
arrT* func(int i);//func返回一個指向含有10個整數的陣列的指針

宣告一個返回陣列指針的函數,要想在宣告func使不使用型別別名,我們必須牢記被定義的名字後面陣列的維度。

int arr[10];
int *p1[10];
int (*p2)[10] = &aar;//p2是一個指針,它指向含有10個整數的陣列
int (*func(int i))[10];
1.func(int i)表示呼叫func函數時需要一個int型別的實參
2.(*func(int i))意味着我們可以對函數的呼叫結果執行解除參照的操作
3.(*func(int i))[10]表示解除參照func的呼叫將得到一個大小是10的陣列
4.int (*func(int i))[10]表示陣列中元素型別是int型別

尾置型別,下面 下麪這個例子我們把函數的返回型別放在了行參列表之後,所以可以清楚的看到func函數返回是一個指針,並且該指針指向含有10個整數的陣列。

//fun接受一個int型別的實參,返回一個指針,該指針指向含有10個int值的整數的陣列
auto func(int i) ->int(*)[10];

使用decltype,如果我們知道函數返回的指針將指向哪個陣列,就可以使用decltype關鍵字宣告返回型別。

int odd[] = {1,3,5,7,9};
int even[] ={0,2,4,6,8};
decltype(odd) *arrPtr(int i)
{
	return (i % 2)? &odd : &even;//返回一個指向陣列的指針
}
//attrptr使用關鍵字decltype表示它的返回型別是個指針並且該指針所指的物件型別與odd的型別一致
//因爲odd是陣列,所以arrptr返回一個指向含有5個整數的陣列的指針。
//需要注意的是:decltype並不負責把陣列的型別轉換成對應的指針,所以decltype的結果是個陣列。
//要想表示arrptr返回指針還必須在函數宣告前加一個*符號

函數過載

對於過載函數來說,它們應該在行參數量或行參型別上有所不同。不允許兩個函數除了返回型別外其他有要素都相同。

Record lookup(phone);
Record lookup(const phone);//重複宣告瞭Record lookup(phone);
Record lookup(phone*);
Record lookup(phone* const);//重複宣告瞭Record lookup(phone*);

//對於接受參照或指針的函數來說,物件是常數還是非常數對於的行參不同
Record lookup(Account&);//函數作用於account的參照
Record lookup(const Account&);//新函數,作用於常數參照
Record lookup(Account*);//新函數,作用於指向account的指針
Record lookup(const Account*);//新函數,作用於指向常數的指針

當我們要傳遞一個非常數物件或者指向非常數物件的指針時,編譯器會優先選用非常數版本的函數。

重載於作用域

string read();
void print(const string &);
void print(double);
void fooBar(int ival)
{
	bool read = false;//新作用域,隱藏了外層的read
	string s = read();//錯誤,read是一個bool值,而非一個函數
	void print(int);//不好的習慣,在區域性作用域中宣告函數把不是一個好的選擇
	print("value: ");//錯誤,void print(const string &);被隱藏了
	print(ival);//正確
	print(3.14); //正確,實際上呼叫了void print(int);double型別的實參轉換成int型別
}

特殊用途語言特性

預設實參
某些函數有這樣一種行參,在函數的很多次呼叫中它們都被賦予一個相同的值。

typedef string::size_type sz;
string screen(sz ht = 24,sz wid = 80,char backgrnd = ' ');
string window;
window  = screen();//等價於screen(24,80,‘ ’)
window = screen(66);//等價於screen(66,80,‘’)
window = screen(66,256);//等價於screen(66,256,‘’)
window = screen(66,256,'#');//等價於screen(66,256,‘#’)

預設實參宣告
函數的後續宣告只能爲之前那些沒有預設值的形參新增預設實參,而且該行參右側的所有行參必須都有預設值。

string screen(sz,sz,char = ' ');
string screen(sz,sz,char = ' *')//錯重複宣告;
//但是可以按照如下形式新增預設實參
string screen(sz = 24,sz = 80,char);

行內函式 inline
呼叫函數一般比求等價表達式的值要慢一些,在大多數的機器上,一次函數呼叫包含着一系列的工作,呼叫前要先儲存暫存器,並且返回時恢復;可能需要拷貝實參,程式轉向一個新的位置繼續執行。

inline const string& shorterString(const string &s1,const string &s2)
{
	return s1.size()<= s2.size() ? s1:s2;
}
//呼叫
//如果不是內聯的話是這樣的:
cout<< shorterString(s1,s2)<<endl;
//如果使用了內聯是這樣的:
count<< (s1.size()<s2.size()?s1:s2)<,endl;

constexpr函數是指用於常數表達式的函數。使用constexpr函數要遵循一下約定
1.函數的返回型別以及所有行參的型別都得是字面值型別
2.函數體中必須有且只有一條return語句

constexpr int new_sz(){ return 42;}
constexpr int foo  = =new_sz();

函數指針

函數指針指向的是函數而非物件。函數的型別由它的返回型別和形參共同決定,與函數名無關。

//該函數的型別bool(const string &,const string &)
bool lengthCompare(const string &,const string &);

//想要宣告一個可以指向該函數的指針,只需要用指針替換函數名即可:
bool (*pf)(const string&,const string &);//未初始化

pf = lengthCompare;//pf指向名爲lengthCompare函數
pf = &lengthCompare;//等價的賦值語句

//我們可以直接使用指向函數的指針呼叫該函數,無須提前解除參照指針:
bool b1 = pf("hello","goodbye");
bool b2 = (*pf)("hello","goodbye");

//我們可以爲函數指針賦一個nullptr或者值爲0的整型常數表達式,表示該指針沒有指向任何一個函數。
string::size_type sumLength(const string &,const string &);
bool cstringCompare(const char*,const char*);
pf = 0 ;//正確,pf不指向任何函數
pf = sumlength;//錯誤,返回型別不匹配
pf = cstringCompare;//錯誤,形參型別不匹配
pf = lengthCompare;//正確,函數和指針的型別精確匹配

函數指針形參

void useBigger(const string &s1,const string &s2,bool pf (const string &,const string &));
//等價宣告:顯示的將形參定義成指向函數的指針
void useBigger(cosnt string &s1,const string &s2,bool (*pf )(const string &,cosnt string &));
//使用型別別名簡化使用函數指針
typedef bool Func(const string &,const string &);
typedef  decltype (lengthCompare)Func2;

typedef bool (*FuncP)(const string &,const string &);
typedef decltype (lengthCompare)*FuncP2;

//useBigger的等價宣告,其中使用了型別別名
void useBigger(const string &,const string &,Func);
void useBigger(const string &,const string &,FuncP2);

返回指向函數的指針

using F = int (int *,int );
using PF = int(*)(int *,int );
PF  f1(int);//正確,PF是指向函數的指針,f1返回指向函數的指針
F f1(int);//錯誤,F是函數型別,f1不能返回一個函數
F *f1(int)//正確,顯示的指定返回型別是指向函數的指針