面試題:C++ 對C的擴展最全總結

 

c++語言在c語言的基礎上添加了面向對象編程泛型編程的支持。c++繼承了c語言高效,簡潔,快速和可移植的傳統。

c++融合了3種不同的編程方式:

  • c語言代表的過程性語言.
  • c++在c語言基礎上添加的類代表的面嚮對象語言.
  • c++模板支持的泛型編程。

目錄

一、 ::作用域運算符

二、 名字控制

2.1 C++命名空間(namespace)

2.2 using聲明

三、全局變量檢測增強

四、 C++中所有的變量和函數都必須有類型

五、更嚴格的類型轉換

六、struct類型加強

七、“新增”bool類型關鍵字

八、三目運算符功能增強

九、 C/C++中的const

 9.1 C中的const

9.2 C++中的const

9.3 C/C++中const異同總結

9.4 儘量以const替換#define

十、引用

10.1 引用基本用法

10.2 函數中的引用

10.3 引用的本質

10.4 指針引用

10.5 常量引用

十一、 內聯函數(inline function)

11.1 類內部的內聯函數

11.2 內聯函數和編譯器

十二、函數的默認參數

十三、函數的佔位參數

十四、函數重載(overload)


 

 

 

一、 ::作用域運算符

通常情況下,如果有兩個同名變量,一個是全局變量,另一個是局部變量,那麼局部變量在其作用域內具有較高的優先權,它將屏蔽全局變量。

//全局變量
int a = 10;
//1. 局部變量和全局變量同名
void test(){
	int a = 20;
	//打印局部變量a
	cout << "局部變量a:" << a << endl;
	//打印全局變量a
	cout << "全局變量a:" << ::a << endl;
}

 

二、 名字控制

創建名字是程序設計過程中一項最基本的活動,當一個項目很大時,它會不可避免地包含大量名字。c++允許我們對名字的產生和名字的可見性進行控制。

 

2.1 C++命名空間(namespace)

  • 創建一個命名空間:
namespace A{
	int a = 10;
}
namespace B{
	int a = 20;
}
void test(){
	cout << "A::a : " << A::a << endl;
	cout << "B::a : " << B::a << endl;
}

注意:

  • 命名空間只能全局範圍內定義
  • 命名空間可嵌套命名空間
  • 命名空間是開放的,即可以隨時把新的成員加入已有的命名空間中
  • 聲明和實現可分離
  • 無名命名空間,意味着命名空間中的標識符只能在本文件內訪問,相當於給這個標識符加上了static,使得其可以作爲內部連接

 

2.2 using聲明

如果命名空間包含一組用相同名字重載的函數,using聲明就聲明瞭這個重載函數的所有集合。

namespace A{
	int paramA = 20;
	int paramB = 30;
	void funcA(){ cout << "hello funcA" << endl; }
	void funcB(){ cout << "hello funcA" << endl; }
}

void test(){
	//1. 通過命名空間域運算符
	cout << A::paramA << endl;
	A::funcA();
	//2. using聲明
	using A::paramA;
	using A::funcA;
	cout << paramA << endl;
	//cout << paramB << endl; //不可直接訪問
	funcA();
}

 

三、全局變量檢測增強

c語言代碼:

int a = 10; //賦值,當做定義
int a; //沒有賦值,當做聲明

int main(){
	printf("a:%d\n",a);
	return EXIT_SUCCESS;
}

此代碼在c++下編譯失敗,在c下編譯通過.

 

四、 C++中所有的變量和函數都必須有類型

  1. C語言中,int fun() 表示返回值爲int,接受任意參數的函數,int fun(void) 表示返回值爲int的無參函數。
  2. 在C++ 中,int fun() 和int fun(void) 具有相同的意義,都表示返回值爲int的無參函數。

 

五、更嚴格的類型轉換

在C++,不同類型的變量一般是不能直接賦值的,需要相應的強轉。

 

六、struct類型加強

  1. c中定義結構體變量需要加上struct關鍵字,c++不需要。
  2. c中的結構體只能定義成員變量,不能定義成員函數。c++即可以定義成員變量,也可以定義成員函數。
//1. 結構體中即可以定義成員變量,也可以定義成員函數
struct Student{
	string mName;
	int mAge;
	void setName(string name){ mName = name; }
	void setAge(int age){ mAge = age; }
	void showStudent(){
		cout << "Name:" << mName << " Age:" << mAge << endl;
	}
};

//2. c++中定義結構體變量不需要加struct關鍵字
void test01(){
	Student student;
	student.setName("John");
	student.setAge(20);
	student.showStudent();
}

 

七、“新增”bool類型關鍵字

標準c++的bool類型有兩種內建的常量true(轉換爲整數1)和false(轉換爲整數0)表示狀態。這三個名字都是關鍵字。

  1. bool類型只有兩個值,true(1值),false(0值)
  2. bool類型佔1個字節大小
  3. 給bool類型賦值時,非0值會自動轉換爲true(1),0值會自動轉換false(0)

 

八、三目運算符功能增強

  1. c語言三目運算表達式返回值爲數據值,爲右值,不能賦值。
  2. c++語言三目運算表達式返回值爲變量本身(引用),爲左值,可以賦值。
        int a = 10;
	int b = 20;
	printf("ret:%d\n", a > b ? a : b);


	cout << "b:" << b << endl;
	//返回的是左值,變量的引用
	(a > b ? a : b) = 100;//返回的是左值,變量的引用
	cout << "b:" << b << endl;

 

九、 C/C++中的const

 

 9.1 C中的const

C中的const 是僞常量,

const int arrSize = 10;
int arr[arrSize];

看似是一件合理的編碼,但是這將得出一個錯誤。 因爲arrSize佔用某塊內存,所以C編譯器不知道它在編譯時的值是多少?

9.2 C++中的const

在c++中,一個const不必創建內存空間,而在c中,一個const總是需要一塊內存空間。

一般說來,如果一個const僅僅用來把一個名字用一個值代替(就像使用#define一樣),那麼該存儲局空間就不必創建。

不過,取一個const地址, 或者把它定義爲extern,則會爲該const創建內存空間。

 在c++中,出現在所有函數之外的const作用於整個文件(也就是說它在該文件外不可見),默認爲內部連接,c++中其他的標識符一般默認爲外部連接。

 

9.3 C/C++中const異同總結

  • c語言全局const會被存儲到只讀數據段。c++中全局const當聲明extern或者對變量取地址時,編譯器會分配存儲地址,變量存儲在只讀數據段。兩個都受到了只讀數據段的保護,不可修改。
  • c語言中局部const存儲在堆棧區,只是不能通過變量直接修改const只讀變量的值,但是可以跳過編譯器的檢查,通過指針間接修改const值。
  • c++中對於局部的const變量要區別對待:
  1. 對於基礎數據類型,也就是const int a = 10這種,編譯器會把它放到符號表中,不分配內存,當對其取地址時,會分配內存。
  2. 對於基礎數據類型,如果用一個變量初始化const變量,如果const int a = b,那麼也是會給a分配內存。
  3. 對於自定數據類型,比如類對象,那麼也會分配內存。
  • c中const默認爲外部連接,c++中const默認爲內部連接

 

9.4 儘量以const替換#define

  1. const有類型,可進行編譯器類型安全檢查。#define無類型,不可進行類型檢查.
  2. const有作用域,而#define不重視作用域,默認定義處到文件結尾.如果定義在指定作用域下有效的常量,那麼#define就不能用。

 

十、引用

引用是c++對c的重要擴充。在c/c++中指針的作用基本都是一樣的,但是c++增加了另外一種給函數傳遞地址的途徑,這就是按引用傳遞(pass-by-reference),它也存在於其他一些編程語言中,並不是c++的發明。

c++中新增了引用的概念,引用可以作爲一個已定義變量的別名。

注意事項:

  • &在此不是求地址運算,而是起標識作用。
  • 類型標識符是指目標變量的類型
  • 必須在聲明引用變量時進行初始化。
  • 引用初始化之後不能改變。
  • 不能有NULL引用。必須確保引用是和一塊合法的存儲單元關聯。
  • 可以建立對數組的引用。

 

10.1 引用基本用法

1. 認識引用

void test01(){

	int a = 10;
	//給變量a取一個別名b
	int& b = a;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "------------" << endl;
	//操作b就相當於操作a本身
	b = 100;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "------------" << endl;
	//一個變量可以有n個別名
	int& c = a;
	c = 200;
	cout << "a:" << a << endl;
	cout << "b:" << b << endl;
	cout << "c:" << c << endl;
	cout << "------------" << endl;
	//a,b,c的地址都是相同的
	cout << "a:" << &a << endl;
	cout << "b:" << &b << endl;
	cout << "c:" << &c << endl;
}

2. 使用引用注意事項

void test02(){
	//1) 引用必須初始化
	//int& ref; //報錯:必須初始化引用
	//2) 引用一旦初始化,不能改變引用
	int a = 10;
	int b = 20;
	int& ref = a;
	ref = b; //不能改變引用
	//3) 不能對數組建立引用,格式錯誤
	int arr[10];
	//int& ref3[10] = arr;
}

3.建立數組引用

//1. 建立數組引用方法一
typedef int ArrRef[10];
int arr[10];
ArrRef& aRef = arr;
for (int i = 0; i < 10;i ++)
{
	aRef[i] = i+1;
}
for (int i = 0; i < 10;i++)
{
	cout << arr[i] << " ";
}
cout << endl;

//2. 建立數組引用方法二
int(&f)[10] = arr;
for (int i = 0; i < 10; i++)
{
	f[i] = i+10;
}
for (int i = 0; i < 10; i++)
{
	cout << arr[i] << " ";
}
cout << endl;

 

10.2 函數中的引用

最常見看見引用的地方是在函數參數和返回值中。我們知道在函數中修改外部的值需要傳入函數高一級的指針,纔可以修改。但是這樣會增加指針的難度,如果使用引用,只需要傳入同級指針。

如果從函數中返回一個引用,必須像從函數中返回一個指針一樣對待。當函數返回值時,引用關聯的內存一定要存在。

例子:用三種方法交換2個的值

//值傳遞(無法交換)
void ValueSwap(int m,int n){
	int temp = m;
	m = n;
	n = temp;
}
//地址傳遞
void PointerSwap(int* m,int* n){
	int temp = *m;
	*m = *n;
	*n = temp;
}
//引用傳遞
void ReferenceSwap(int& m,int& n){
	int temp = m;
        m = n;
	n = temp;
}


void test(){
	int a = 10;
	int b = 20;
	//值傳遞
	ValueSwap(a, b);
	cout << "a:" << a << " b:" << b << endl;
	//地址傳遞
	PointerSwap(&a, &b);
	cout << "a:" << a << " b:" << b << endl;
	//引用傳遞
	ReferenceSwap(a, b);
	cout << "a:" << a << " b:" << b << endl;
}

通過引用參數產生的效果同按地址傳遞是一樣的。引用的語法更清楚簡單:   

  1. 函數調用時傳遞的實參不必加“&”符
  2. 在被調函數中不必在參數前加“*”符

引用作爲其它變量的別名而存在,因此在一些場合可以代替指針。C++主張用引用傳遞取代地址傳遞的方式,因爲引用語法容易且不易出錯。

注意的點:

  1. 不能返回局部變量的引用。
  2. 函數當左值,必須返回引用。
//返回局部變量引用
int& TestFun01(){
	int a = 10; //局部變量
	return a;
}
//返回靜態變量引用
int& TestFunc02(){	
	static int a = 20;
	cout << "static int a : " << a << endl;
	return a;
}
int main(){
	//不能返回局部變量的引用
	int& ret01 = TestFun01();
	//如果函數做左值,那麼必須返回引用
	TestFunc02();
	TestFunc02() = 100;
	TestFunc02();

	return EXIT_SUCCESS;
}

 

10.3 引用的本質

引用的本質在c++內部實現是一個指針常量.

Type& ref = val;           // Type* const ref = &val;

c++編譯器在編譯過程中使用常指針作爲引用的內部實現,因此引用所佔用的空間大小與指針相同,只是這個過程是編譯器內部實現,用戶不可見。

//發現是引用,轉換爲 int* const ref = &a;
void testFunc(int& ref){
	ref = 100; // ref是引用,轉換爲*ref = 100
}
int main(){
	int a = 10;
	int& aRef = a; //自動轉換爲 int* const aRef = &a;這也能說明引用爲什麼必須初始化
	aRef = 20; //內部發現aRef是引用,自動幫我們轉換爲: *aRef = 20;
	cout << "a:" << a << endl;
	cout << "aRef:" << aRef << endl;
	testFunc(a);
	return EXIT_SUCCESS;
}

 

10.4 指針引用

在c語言中如果想改變一個指針的指向而不是它所指向的內容,函數聲明可能這樣:

給指針變量取一個別名。

Type* pointer = NULL;  
Type*& = pointer;

例子:修改teacher的年齡

struct Teacher{
	int mAge;
};
//指針間接修改teacher的年齡
void AllocateAndInitByPointer(Teacher** teacher){
	*teacher = (Teacher*)malloc(sizeof(Teacher));
	(*teacher)->mAge = 200;  
}
//引用修改teacher年齡
void AllocateAndInitByReference(Teacher*& teacher){
	teacher->mAge = 300;
}
void test(){
	//創建Teacher
	Teacher* teacher = NULL;
	//指針間接賦值
	AllocateAndInitByPointer(&teacher);
	cout << "AllocateAndInitByPointer:" << teacher->mAge << endl;       //200
	//引用賦值,將teacher本身傳到ChangeAgeByReference函數中
	AllocateAndInitByReference(teacher);
	cout << "AllocateAndInitByReference:" << teacher->mAge << endl;     //300
	free(teacher);
}

對於c++中的定義那個,語法清晰多了。函數參數變成指針的引用,用不着取得指針的地址。

 

10.5 常量引用

常量引用的定義格式:

const Type  ref   val;

常量引用注意:

  1. 字面量不能賦給引用,但是可以賦給const引用
  2. const修飾的引用,不能修改。
void test01(){
	int a = 100;
	const int& aRef = a; //此時aRef就是a
	//aRef = 200; 不能通過aRef的值
	a = 100; //OK
	cout << "a:" << a << endl;
	cout << "aRef:" << aRef << endl;
}

不能把一個字面量賦給引用,但是可以把一個字面量賦給常引用

void test02(){
	//不能把一個字面量賦給引用
	//int& ref = 100;
	//但是可以把一個字面量賦給常引用
	const int& ref = 100;         //解釋器翻譯過來 :int temp = 100; const int& ret = temp;
}

 

    [const引用使用場景]

    常量引用主要用在函數的形參,尤其是類的拷貝/複製構造函數。

將函數的形參定義爲常量引用的好處:

  • 引用不產生新的變量,減少形參與實參傳遞時的開銷。
  • 由於引用可能導致實參隨形參改變而改變,將其定義爲常量引用可以消除這種副作用。

    如果希望實參隨着形參的改變而改變,那麼使用一般的引用,如果不希望實參隨着形參改變,那麼使用常量引用。

 

十一、 內聯函數(inline function)

 

內聯函數爲了繼承宏函數的效率,沒有函數調用時開銷,然後又可以像普通函數那樣,可以進行參數,返回值類型的安全檢查,又可以作爲成員函數。

在c++中,預定義宏的概念是用內聯函數來實現的,而內聯函數本身也是一個真正的函數

內聯函數具有普通函數的所有行爲。唯一不同之處在於內聯函數會在適當的地方像預定義宏一樣展開,所以不需要函數調用的開銷。因此應該不使用宏,使用內聯函數。

在普通函數(非成員函數)函數前面加上inline關鍵字使之成爲內聯函數。但是必須注意必須函數體和聲明結合在一起,否則編譯器將它作爲普通函數來對待。

inline  void  func(int a);

以上寫法沒有任何效果,僅僅是聲明函數,應該如下方式來做:

inline  int  func(int a){return ++;}

內聯函數的確佔用空間,但是內聯函數相對於普通函數的優勢只是省去了函數調用時候的壓棧,跳轉,返回的開銷。我們可以理解爲內聯函數是以空間換時間

 

11.1 類內部的內聯函數

爲了定義內聯函數,通常必須在函數定義前面放一個inline關鍵字。但是在類內部定義內聯函數時並不是必須的。任何在類內部定義的函數自動成爲內聯函數。

class Person{

public:

    Person(){ cout << "構造函數!" << endl; }

    void PrintPerson(){ cout << "輸出Person!" << endl; }

}

構造函數Person,成員函數PrintPerson在類的內部定義,自動成爲內聯函數。

 

11.2 內聯函數和編譯器

但是c++內聯編譯會有一些限制,以下情況編譯器可能考慮不會將函數進行內聯編譯:

  • 不能存在任何形式的循環語句
  • 不能存在過多的條件判斷語句
  • 函數體不能過於龐大
  • 不能對函數進行取址操作

內聯僅僅只是給編譯器一個建議,編譯器不一定會接受這種建議,如果你沒有將函數聲明爲內聯函數,那麼編譯器也可能將此函數做內聯編譯。一個好的編譯器將會內聯小的、簡單的函數。

 

十二、函數的默認參數

c++在聲明函數原型的時可爲一個或者多個參數指定默認(缺省)的參數值,當函數調用的時候如果沒有指定這個值,編譯器會自動用默認值代替。

void TestFunc01(int a = 10, int b = 20){
	cout << "a + b  = " << a + b << endl;
}
//注意點:
//1. 形參b設置默認參數值,那麼後面位置的形參c也需要設置默認參數
void TestFunc02(int a,int b = 10,int c = 10){}
//2. 如果函數聲明和函數定義分開,函數聲明設置了默認參數,函數定義不能再設置默認參數
void TestFunc03(int a = 0,int b = 0);
void TestFunc03(int a, int b){}

int main(){
	//1.如果沒有傳參數,那麼使用默認參數
	TestFunc01();
	//2. 如果傳一個參數,那麼第二個參數使用默認參數
	TestFunc01(100);
	//3. 如果傳入兩個參數,那麼兩個參數都使用我們傳入的參數
	TestFunc01(100, 200);

	return EXIT_SUCCESS;
}

 

  1. 函數的默認參數從左向右,如果一個參數設置了默認參數,那麼這個參數之後的參數都必須設置默認參數。
  1. 如果函數聲明和函數定義分開寫,函數聲明和函數定義不能同時設置默認參數。

 

十三、函數的佔位參數

c++在聲明函數時,可以設置佔位參數。佔位參數只有參數類型聲明,而沒有參數名聲明。一般情況下,在函數體內部無法使用佔位參數。

void TestFunc01(int a,int b,int){
	//函數內部無法使用佔位參數
	cout << "a + b = " << a + b << endl;
}
//佔位參數也可以設置默認值
void TestFunc02(int a, int b, int = 20){
	//函數內部依舊無法使用佔位參數
	cout << "a + b = " << a + b << endl;
}
int main(){

	//錯誤調用,佔位參數也是參數,必須傳參數
	//TestFunc01(10,20); 
	//正確調用
	TestFunc01(10,20,30);
	//正確調用
	TestFunc02(10,20);
	//正確調用
	TestFunc02(10, 20, 30);

	return EXIT_SUCCESS;
}

 

十四、函數重載(overload)

實現函數重載的條件:

  1. 同一個作用域
  2. 參數個數不同
  3. 參數類型不同
  4. 參數順序不同
//1. 函數重載條件
namespace A{
	void MyFunc(){ cout << "無參數!" << endl; }
	void MyFunc(int a){ cout << "a: " << a << endl; }
	void MyFunc(string b){ cout << "b: " << b << endl; }
	void MyFunc(int a, string b){ cout << "a: " << a << " b:" << b << endl;}
    void MyFunc(string b, int a){cout << "a: " << a << " b:" << b << endl;}
}

//2.返回值不作爲函數重載依據
namespace B{
	void MyFunc(string b, int a){}
	//int MyFunc(string b, int a){} //無法重載僅按返回值區分的函數
}

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章