C++初階(名稱空間+預設引數+const總結+參照總結+行內函式+auto關鍵字)

2022-11-14 21:01:15

名稱空間

概述

在C/C++中,變數、函數和後面要學到的類都是大量存在的,這些變數、函數和類的名稱將都存在於全域性作用域中,可能會導致很多衝突。使用名稱空間的目的是對識別符號的名稱進行在地化,以避免命名衝突或名字汙染,namespace關鍵字的出現就是針對這種問題的。

舉個例子:

#include <stdio.h>
#include <stdlib.h>

int rand = 10;

int main()
{
	printf("%d", rand);
	return 0;
}

程式編譯的結果顯示rand重定義了,為什麼會這樣呢?因為在stdlib.h這個標頭檔案中已經定義了rand這樣一個函數,這樣就導致了編譯器不知道這是一個函數還是一個變數,C語言中無法應對這種衝突,只能通過改名字來避免。

而C++為了解決這個問題,引入了名稱空間的概念。

名稱空間的定義

定義名稱空間,需要使用到namespace關鍵字,後面跟名稱空間的名字,然後接一對{}即可,{}中即為名稱空間的成員。

//名稱空間
//A就是名稱空間的名字
namespace A{
	int a;
	void func()
	{}
}

注意事項:

  • 名稱空間只能寫在全域性
  • 名稱空間可以巢狀名稱空間
  • 名稱空間是開放的,隨時可以加入新的成員,但是新加入的成員只能在加入後使用
  • 匿名名稱空間就類似於static
  • 名稱空間可以取別名
  • 分檔案編寫的時候,如果標頭檔案有兩個名稱空間,但是裡面的成員函數或者成員變數同名的時候,在cpp重實現函數需要加上名稱空間

接下來給一個完整的程式碼塊來展示名稱空間的注意事項和使用:

#include<iostream> //引入標頭檔案
#include<string>//C++中的字串
using namespace std; //標準名稱空間
//A就是名稱空間的名字
namespace A
{	
	//名稱空間內既可以定義變數也可以定義函數
	int rand = 10;
	int Sub(int x, int y)
	{
		return x + y;
	}
	struct ListNode
	{
		int data;
		struct ListNode* next;
	};
}
//名稱空間的巢狀定義
namespace B
{
	int rand;
	namespace C
	{
		int rand;
	}
}
//名稱空間是開放的,隨時可以加入新成員,但是新成員只能在加入後使用
namespace B
{
	int c;//此時c會合併到名稱空間B中,實際上就是個合併的過程
}
//匿名名稱空間
namespace 
{
	int d = 5;//名稱空間沒有名字,就類似於static int d = 50,是個靜態全域性變數,別的檔案無法使用
}
int main()
{
	//名稱空間的使用
	//1.::作用域限定符
	//存取A空間的Sub函數
	cout << A::Sub(10, 20) << endl;
	//2.存取巢狀空間
	//存取B空間的C空間的rand變數
	B::C::rand = 5;
	cout << B::C::rand << endl;

	system("pause");
	return EXIT_SUCCESS;
}

using關鍵字

引入using關鍵字之後,名稱空間的使用又變得不一樣

  • 用using將名稱空間的成員引入
namespace A
{
	int a = 10;
}
void test01()
{
	//using宣告的意思就是讓名稱空間中某個識別符號可以直接使用
	using A::a;
	cout<<a<<endl;
}

注意:

1.使用using宣告,就不會每次寫A::a了,直接用a就可以

2.using A::a宣告的意思就是把變數a又在test函數中定義了一次,此時如果在test內部再定義一個int a;就會出錯

  • 用using namespace 名稱空間名稱引入
namespace A
{
	int a = 10;
}
using namespace A;
void test01()
{
	cout<<a<<endl;
}

使用using關鍵字修飾namespace整個名稱空間,實際上就是脫去了這個名稱空間的外衣,就等價於你定義了一個int a在全域性

思考一個問題:下面程式碼有錯嗎?

在test01函數體內又定義了一個int a,會報錯麼?如果不報錯,那麼輸出的是全域性的 a = 10 還是區域性的a = 20?

namespace A
{
	int a = 10;
}
using namespace A;
void test01()
{
	int a = 20;
	cout<<a<<endl;
}

答案是不會報錯,輸出的是區域性的20,因為名稱空間A內部的變數a在使用using關鍵字後相當於在全域性定義了一個int a ;而在函數體內定義一個區域性的 int a;兩個變數的作用域不同,是可以定義同名變數的,輸出的是區域性變數的值,小夥伴的要注意區分~

C++輸入和輸出

  • C語言用的是printf和scanf進行輸入和輸出的。那麼C++是用什麼來進行輸入輸出的呢?
  • C++用到的是cout(控制檯)和cin(鍵盤)兩個函數進行操作,使用是必須包含iostream的標頭檔案及 std標準名稱空間。
  • C++標頭檔案不帶.h,將std標準名稱空間進行展開。
#include <iostream>
using namespace std;// 將std標準名稱空間進行展開

int main()
{
	cout << "hello world" << endl;
	// std::cout << "hello world" << endl; 也可以這樣寫就不展開std標準名稱空間
	return 0;
}

使用C++輸入輸出更方便,不需增加資料格式控制,比如:整形–%d,字元–%c

int main()
{
	int a = 0;
	double b = 0.0;

	cin >> a;
	cin >> b;

	cout << "a = " << a << " b = " << b << endl;
	system("pause");
	return EXIT_SUCCESS;
}

執行結果如下:

預設引數

概念:

預設引數是宣告或定義函數時為函數的引數指定一個預設值。在呼叫該函數時,如果沒有指定實參則採用該預設值,否則使用指定的實參。

void PrintNum(int n = 0)
{
	cout << n << endl;
}

int main()
{
	PrintNum();// 無引數時,使用引數的預設值
	PrintNum(10);// 有引數時,使用指定的實參
	system("pause");
	return EXIT_SUCCESS;
}

全預設引數

引數都要一個預設值,給定的實參依次從左向右給形參賦值

注意:我們在呼叫函數時,只能預設最右邊的若干個引數,形如:Fun(4, , 6);這種呼叫是錯誤的呼叫方法。

void Func(int a = 10, int b = 20, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}


int main()
{
	// 實參從左向右一次給形參賦值
	Func();
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);
	system("pause");
	return EXIT_SUCCESS;
}

執行結果如下:

半預設引數

只有部分形參給定了預設值,半預設引數必須從右往左依次來給出,不能間隔著給。

值得注意的是,預設引數只能為最右邊的若干個

形如:void Fun(int a=10, int b, int c = 30) { }這樣的語句是錯誤的用法。

形如:Fun(1, ,3)這種呼叫也是錯誤的。

void Func(int a, int b = 10, int c = 30)
{
	cout << "a = " << a << endl;
	cout << "b = " << b << endl;
	cout << "c = " << c << endl;
}

int main()
{
	Func(1);
	Func(1, 2);
	Func(1, 2, 3);
	system("pause");
	return EXIT_SUCCESS;
}

執行結果如下:

注意:

  • 半預設引數必須從右向左依次給出,不能間隔著給
  • 預設引數不能在宣告中和定義中同時出現(推薦寫在宣告中)
  • 預設引數必須是全域性變數和常數
  • C語言中不支援預設引數

const限定符

const修飾符的作用

  • const型別定義: 指明變數或物件的值是不能被更新,引入目的是為了取代預編譯指令
  • 可以保護被修飾的東西,防止意外的修改,增強程式的健壯性
  • 編譯器通常不為普通const常數分配儲存空間,而是將它們儲存在符號表中,這使得它成為一個編譯期間的常數,沒有了儲存與讀記憶體的操作,使得它的效率也很高
  • 可以節省空間,避免不必要的記憶體分配

規則

  • const離誰近,誰就不能被修改
  • const修飾一個變數時,一定要給這個變數初始化,若不初始化,在後面也不能初始化

分類

  常變數:  const 型別說明符 變數名

  常參照:  const 型別說明符 &參照名

  常物件:  類名 const 物件名

  常成員函數:  類名::fun(形參) const

  常陣列:  型別說明符 const 陣列名[大小]    

  常指標:  const 型別說明符* 指標名 ,型別說明符* const 指標名

const全域性/區域性變數

C

在C語言中const修改全域性變數是儲存在全域性區(即靜態儲存區),修飾區域性變數時儲存在棧區

//const修飾的常數
const int a = 10;//全域性const常數,放在常數區,受到常數區的保護
void test01()
{
	//直接修改失敗
	a = 100;
	//間接修改失敗
	int *p = &a;
	*p = 100;
}
  • 全域性的const修飾的變數本質就是常數,全域性const修飾的變數放在常數區中,不能通過變數名直接修改也不可以通過地址來間接修改
//區域性conts修飾常數
void test02()
{
	conts int b = 10;//資料放在棧區,是個偽常數
	//直接修改失敗
	b = 100;
	//間接修改成功
	int *p = &b;
	*p = 100;
}
  • 區域性const修飾的變數是個偽常數,不是真正意義上的常數,資料存放在棧區而不是常數區,可以間接修改但是不能直接修改。

總結:

  • C語言的const修飾的全域性變數和區域性變數都有空間
  • C語言的const修飾的全域性變數具有外部連結屬性,可以採用extern宣告在別的檔案中使用

C++

在C++中編譯器會自動優化,會將常數的數值直接替換(類似於宏定義),這導致了const區域性變數與真實值產生了不一致。(常數摺疊現象),而C語言會先去記憶體中尋找,然後替換

舉個例子:

const int aa = 10;//沒有記憶體
void test01()
{
	cout << aa << endl;//在編譯階段,編譯器會自動優化,將aa直接替換成常數10
	const int bb = 20;//棧區
	int *p = (int *)&bb;
	*p = 200;
	cout << bb << endl;//輸出的還是20,還是那句話,在編譯階段程式碼中的bb就已經全部被替換成20,此時其實輸出的是這樣的cout << 20 << endl;但是變數bb此時的值已經被改變了,變成了200,但是由於編譯器優化,造成了常數摺疊現象
}

總結:

  • C++語言的const修飾的變數有時有空間,有時沒有空間(發生常數摺疊,且沒有對變數進行取地址操作)
  • C++中,const修飾的全域性變數具有內部連結屬性,也就是說,無法使用別的檔案的const修飾的變數,但是這種規則依舊可以打破
  • const修飾的全域性變數永遠都沒有記憶體,永遠無法修改它的值,但是const修飾的區域性變數可以有空間,可以修改它的值

編譯器不能優化的情況

  • 不能優化自定義資料型別
  • 如果用變數給const修飾的區域性變數賦值,那麼編譯器也不能優化
  • 使用extern和voaltile關鍵字來阻止優化

例子一:用變數給const修飾的區域性變數賦值

void test03()
{
	int a =10;
	const int b = a;
	int *p = (int *)&b;
	*p = 100;
	cout << b << endl;//輸出100
}

例子二:利用關鍵字阻止優化

void test04()
{
	const volatile int a = 7;
	int *p = (int *)(&a);
	*p = 8;
	cout << "a=" << a << endl;//輸出8
	cout << "*p=" << *p;
	system("pause");
	return 0;
}

例子三:自定義資料型別不能優化

struct Maker
{
	Maker()
	{
		a = 100;
	}
	int a;
};
void test05()
{
	const Maker ma;
	cout << ma.a <<endl;
	Maker *p = (Maker*)&ma;
	p->a = 200;//可以修改ma中的值
	cout << ma.a << endl;
}

const修飾指標和參照

const修飾指標

涉及到兩個很重要的概念,頂層const底層const

從 const 指標開始說起。const int* pInt;int *const pInt = &someInt;,前者是 *pInt 不能改變,而後者是 pInt 不能改變。因此指標本身是不是常數和指標所指向的物件是不是常數就是兩個互相獨立的問題。用頂層表示指標本身是個常數,底層表示指標所指向的物件是個常數。
更一般的,頂層 const 可以表示任意的物件是常數,這一點對任何資料型別都適用;底層 const 則與指標和參照等複合型別有關,比較特殊的是,指標型別既可以是頂層 const 也可以是底層 const 或者二者兼備。

int a = 1;
int b = 2;
const int* p1 = &a;//指標常數(頂層const)
int* const p2 = &a;//常數指標(底層const)
1.指標常數(指標不可改,指標指向的物件可改)

int a = 10;
int b = 5;
int * const p1 = &a;
p1 = &b; //指標不可改,不合法
*p1 = b; //指標指向的物件可改,合法

2.常數指標(指標可改,指標指向的物件不可改)
int a = 10;
int b = 5;
const int* p2 = &a;
p2 = &b; //指標可改, 合法
*p2 = b; //不合法

拷貝與頂層和底層 const

int i = 0;
int *const p1 = &i;     //  不能改變 p1 的值,這是一個頂層
const int ci = 42;      //  不能改變 ci 的值,這是一個頂層
const int *p2 = &ci;    //  允許改變 p2 的值,這是一個底層
const int *const p3 = p2;   //  靠右的 const 是頂層 const,靠左的是底層 const
const int &r = ci;      //  所有的參照本身都是頂層 const,因為參照一旦初始化就不能再改為其他物件的參照,這裡用於宣告參照的 const 都是底層 const

const修飾參照

常參照所參照的物件不能更新,使用方法為:const 型別說明符 &參照名

非const參照只能繫結非const物件,const參照可以繫結任意物件,並且都當做常物件

常參照經常用作形參,防止函數內物件被意外修改。對於在函數中不會修改其值的引數,最好都宣告為常參照。複製建構函式的引數一般均為常參照

class Example{
public:
    Example(int x, int y):a(x),b(y){}
    Example(const Example &e):a(e.a),b(e.b){} //複製建構函式
    void print();
    void print() const;
private:
    const int a,b;
    static const int c = 10;
};
void Example::print() {cout<<"print():"<<a<<ends<<b<<endl;}
void Example::print() const {cout<<"print() const:"<<a<<ends<<b<<endl;}

const修飾函數引數

const修飾引數是為了防止函數體內可能會修改引數原始物件。因此,有三種情況可討論:

  • 1、函數引數為值傳遞:值傳遞(pass-by-value)是傳遞一份引數的拷貝給函數,因此不論函數體程式碼如何執行,也只會修改拷貝而無法修改原始物件,這種情況不需要將引數宣告為const。
  • 2、函數引數為指標:指標傳遞(pass-by-pointer)只會進行淺拷貝,拷貝一份指標給函數,而不會拷貝一份原始物件。因此,給指標引數加上頂層const可以防止指標指向被篡改,加上底層const可以防止指向物件被篡改。
  • 3、函數引數為參照:參照傳遞(pass-by-reference)有一個很重要的作用,由於參照就是物件的一個別名,因此不需要拷貝物件,減小了開銷。這同時也導致可以通過修改參照直接修改原始物件(畢竟參照和原始物件其實是同一個東西),因此,大多數時候,推薦函數引數設定為pass-by-reference-to-const。給參照加上底層const,既可以減小拷貝開銷,又可以防止修改底層所參照的物件。
void Fun(const A *in); //修飾指標型傳入引數
void Fun(const A &in); //修飾參照型傳入引數

void func (const int& n)
{
     n = 10;        // 編譯錯誤 
}

const修飾函數返回值

const修飾函數返回值的含義和用const修飾普通變數以及指標的含義基本相同。這樣可以防止外部對 object 的內部成員進行修改。

const int* func()   // 返回的指標所指向的內容不能修改
{
    // return p;
}

const成員函數和資料成員

類的常成員函數

由於C++會保護const物件不被更新,為了防止類的物件出現意外更新,禁止const物件呼叫類的非常成員函數。因此,常成員函數為常物件的唯一對外介面。

常成員函數的宣告方式:型別說明符 函數名(參數列) const

  • const物件只能存取const成員函數,而非const物件可以存取任意的成員函數,包括const成員函數
  • const物件的成員是不能修改的,而通過指標維護的物件卻是可以修改的
  • const成員函數不可以修改物件的資料,不管物件是否具有const性質。編譯時以是否修改成員資料為依據進行檢查
class A
{
public:
	//返回值的型別是int &型別
    int& getValue() const
    {
        // a = 10;    // 錯誤
        return a;
    }

private:
    int a;            // 非const成員變數
};

注意事項:

  • 常成員函數的定義和宣告都要含有const關鍵字
  • 一個函數是否含有const關鍵字可以作為過載函數,const物件預設呼叫const函數,非const物件預設呼叫非const函數,如果沒有非const函數,也可以呼叫const函數
  • const函數中不能更新目的物件的任何成員(mutable修飾的變數除外,這裡不展開闡述),以此方法來保證const物件不被修改
  • 如果const成員函數想修改成員變數值,可以用mutable修飾目標成員變數

類的常資料成員

類的資料成員不能在任何函數中被賦值或修改,但必須在建構函式中使用初始化列表的方式賦初值,因為const修飾的物件必須初始化。
舉個例子,剛才的類如果a, b為常資料成員,則應該改寫為如下形式:

class Example{
public:
    Example(int x, int y):a(x),b(y){} //初始化列表方式賦初值
    void print();
    void print() const;
private:
    const int a,b;
};
void Example::print() {cout<<"print():"<<a<<ends<<b<<endl;}
void Example::print() const {cout<<"print() const:"<<a<<ends<<b<<endl;}

如果為靜態常資料成員,由於不屬於具體物件,所以不能在建構函式裡賦值,仍然應該在類外賦值。特別地,如果靜態常數為整數或列舉型別,C++允許在類內定義時指定常數值。
比如以下兩種方式均合法:

class Example{
public:
    Example(int x, int y):a(x),b(y){}
    void print();
    void print() const;
private:
    const int a,b;
    static const int c = 10; //靜態常數
};
class Example{
public:
    Example(int x, int y):a(x),b(y){}
    void print();
    void print() const;
private:
    const int a,b;
    static const int c; //靜態常數
};
const int Example::c = 10;

const修飾類物件

用const修飾的類物件,該物件內的任何成員變數都不能被修改。
因此不能呼叫該物件的任何非const成員函數,因為對非const成員函數的呼叫會有修改成員變數的企圖。

class A
{
 public:
    void funcA() {}
    void funcB() const {}
};
int main
{
    const A a;
    a.funcB();    // 可以
    a.funcA();    // 錯誤

    const A* b = new A();
    b->funcB();    // 可以
    b->funcA();    // 錯誤
}

const與宏定義的區別

(1) 編譯器處理方式不同
  define宏是在預處理階段展開。
  const常數是編譯執行階段使用。

(2) 型別和安全檢查不同
  define宏沒有型別,不做任何型別檢查,僅僅是展開。
  const常數有具體的型別,在編譯階段會執行型別檢查。

(3) 儲存方式不同
  define宏僅僅是展開,有多少地方使用,就展開多少次,不會分配記憶體。
  const常數會在記憶體中分配(可以是堆中也可以是棧中)。

(4)作用範圍不同

​ const有作用域,而define不重視作用域,預設定義處到檔案結束,如果定義在指定作用域下有效的常數,那麼define不能用。

(5)const 可以節省空間,避免不必要的記憶體分配。 例如:

  #define PI 3.14159 //常數宏  
    const doulbe Pi=3.14159; //此時並未將Pi放入ROM中 ......  
    double i=Pi; //此時為Pi分配記憶體,以後不再分配!  
    double I=PI; //編譯期間進行宏替換,分配記憶體  
    double j=Pi; //沒有記憶體分配  
    double J=PI; //再進行宏替換,又一次分配記憶體!  

const定義常數從組合的角度來看,只是給出了對應的記憶體地址,而不是像#define一樣給出的是立即數,所以,const定義的常數在程式執行過程中只有一份拷貝,而 #define定義的常數在記憶體中有若干個拷貝。

const與static的區別

static

1、static區域性變數 將一個變數宣告為函數的區域性變數,那麼這個區域性變數在函數執行完成之後不會被釋放,而是繼續保留在記憶體中

2、static 全域性變數 表示一個變數在當前檔案的全域性內可存取

3、static 函數 表示一個函數只能在當前檔案中被存取

4、static 類成員變數 表示這個成員為全類所共有

5、static 類成員函數 表示這個函數為全類所共有,而且只能存取靜態成員變數

static關鍵字的作用

(1)函數體內static變數的作用範圍為該函數體,該變數的記憶體只被分配一次,因此其值在下次呼叫時仍維持上次的值
(2)在模組內的static全域性變數和函數可以被模組內的函數存取,但不能被模組外其它函數存取
(3)在類中的static成員變數屬於整個類所擁有,對類的所有物件只有一份拷貝
(4)在類中的static成員函數屬於整個類所擁有,這個函數不接收this指標,因而只能存取類的static成員變數

const關鍵字的作用

(1)阻止一個變數被改變
(2)宣告常數指標和指標常數
(3)const修飾形參,表明它是一個輸入引數,在函數內部不能改變其值
(4)對於類的成員函數,若指定其為const型別,則表明其是一個常函數,不能修改類的成員變數
(5)對於類的成員函數,有時候必須指定其返回值為const型別,以使得其返回值不為」左值」

參照

概念

參照不是新定義一個變數,而是給已存在變數取了一個別名,編譯器不會為參照變數開闢記憶體空間,它和它參照的變數共用同一塊記憶體空間。也就好比我們給同學取了一個外號一樣。
用法: 型別&參照變數名(物件名)= 參照實體;

int main()
{
	int a = 10;
	int& ra = a;// ra是a的參照

	cout << a << endl;
	cout << ra << endl;

	system("pause");
	return EXIT_SUCESS;
}

特性及注意事項

  • 參照在定義的時候必須初始化
  • 一個變數可有多個參照
  • 參照一個參照一個實體,無法再參照其他實體

Type& ref = val;

注意事項:

  • &在此不是取地址運運算元,而是起到一個標識的作用
  • 型別識別符號指的是目標變數的型別
  • 參照初始化之後不能改變
  • 不能有NULL參照,必須確保參照是和一塊合法的儲存單元關聯
int a = 10;
int b = 20;

//int& ra;//參照必須初始化

int& ra = a;
int& rra = a;

//int& ra = b;//參照一旦初始化,不能改變它的指向
//int& raaa = NULL;//NULL本身就是不合法的,不能繫結不合法的空間

陣列的參照

int arr[] = {1, 2, 3, 4, 5};
//三種方式
//1.定義陣列型別
typedef int(MY_ARR)[5];
MY_ARR &arref = arr;//建立參照,int &b = a

//2.直接定義參照
int(&arref2)[5] = arr;

//3.建立參照陣列型別
typedef int(&MY_ARR3)[5];
MY_ARR3 arref3 = arr;

參照的使用場景

//1.作為函數的引數
void func(int &a, int &b)
{
	cout << a+b << endl;
}
//2.參照作為函數的返回值
int& func2()
{
	int b = 10;//不能返回區域性變數的參照
	int &p = b;
	return p;//錯誤的
}
int& func3()
{
	static int b = 10;
	return b;
}
void test01()
{
	int& q = func2();
	cout << q << endl;
	q = 100;
	cout << q << endl;

	func2() = 200;
	cout << q << endl;
	cout << func2() << endl;
//--------------上面的程式碼都是錯誤的,這裡解釋一下
//int& q = func2();實際上就是int&q = p,但是fun2函數執行完了之後,區域性變數全部被銷燬了,所以int&q = 就指向了一個非法的區域,但是編譯器沒有檢測出來,具體原因是什麼不清楚
	func3() = 100;
	cout << func3() << endl;
}

參照做返回值時,一般分兩種情況:返回的物件未歸還系統和返回的物件歸還系統。如果返回物件不歸還系統,我們可以參照返回,否則就需要傳值返回

想要返回區域性變數:1.把區域性變數寫在堆區 2.把區域性變數寫在靜態區

常參照

注意:

  • 字面不能賦給參照,但是可以賦給const參照
  • const修飾的參照,不能修改
//普通參照
int a = 10;
int &ref = a;
ref = 20;
//int &ref2 = 10不能給字面量取別名,因為10這個字面量在記憶體中沒有空間,儲存在暫存器中

//常參照
const int &ref3 = 10;//可以給const修飾的參照賦予字面量
//編譯器會把上面的程式碼變為:int tmp = 10;const int &ref3 = tmp;

行內函式

inline修飾的函數叫做行內函式,編譯時C++編譯器會在呼叫行內函式的地方展開,沒有函數壓棧的開銷,行內函式提升程式執行的效率。

inline int Add(int x, int y)
{
	return x + y;
}

int main()
{
	int z = Add(1, 2);
	return 0;
}

如果加了inline關鍵字,編譯器在編譯期間會用函數體替換函數的呼叫(類似於宏定義,在編譯階段就替換函數呼叫,將函數呼叫直接展開,減少了呼叫的時間,但是空間消耗巨大)。

下面是內斂函數的幾個特性:

1.inline是一種以空間換時間的做法,省去呼叫函數額開銷。所以程式碼很長或者有迴圈/遞迴的函數不適宜使用作為行內函式。
2.inline對於編譯器而言只是一個建議,編譯器會自動優化,如果定義為inline的函數體內有迴圈/遞迴等等,編譯器優化時會忽略掉內聯(是否稱為行內函式由編譯器決定)。
3.inline不建議宣告和定義分離,分離會導致連結錯誤。因為inline被展開,就沒有函數地址了,連結就會找不到。

例子:一個相同的函數,一個加了inline 關鍵字,一個沒加,加上一個函數要執行10條指令,文兩個函數分別呼叫1000次要執行多少條指令?
普通函數:1000+10(一次呼叫1次指令,加起來就是1000條,每次呼叫都是call函數,函數不展開就是10條)
行內函式:1000*10條指令(展開就是每次呼叫都是10條指令)
所以說,行內函式展開,會讓程式變大,所以程式碼很短的函數可以考慮有內聯,長函數和遞迴函數不適合用內聯。

auto關鍵字

C++11中,標準委員會賦予了auto全新的含義即:auto不再是一個儲存型別指示符,而是作為一個新的型別指示符來指示編譯器,auto宣告的變數必須由編譯器在編譯時期推導而得。

int a = 10;
auto b = a;// 自動推導b的型別為int
auto c = 'c';// 自動推導型別為char

cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
//auto d;必須初始化

有一下幾種用法:

  1. auto與指標和參照結合起來使用(auto和auto*無區別)
int a = 10;
// auto和auto*無區別
auto pa1 = &a;
auto* pa2 = &a;

auto& ra = a;// ==> int& ra = a;

cout << typeid(a).name() << endl;
cout << typeid(pa1).name() << endl;
cout << typeid(pa2).name() << endl;
cout << typeid(ra).name() << endl;

執行結果如下:

  1. 在同一行定義多個變數(這些變數型別必須相同,編譯器只對第一個型別進行推導)
auto a = 3, b = 4;
auto c = 3.4, d = 5.5;
auto i =0, *p = &i;//正確,i是整型,p是整型指標

cout << typeid(a).name() << endl;
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
cout << typeid(d).name() << endl;
cout << typeid(i).name() << endl;
cout << typeid(p).name() << endl;

auto不能推導的兩個常見

  1. auto不能作為函數的引數
  2. auto不能直接用來宣告陣列