1. 程式人生 > >C++基本概念

C++基本概念

首先我們要了解為什麼會有C++的出現?
之所以要有C++,是因為C語言存在一定的缺陷,為了彌補這些缺陷,才出現了C++,除此之外,在C++中還引入了新的語法特性。下面簡單介紹一下:

1.C++關鍵字
學習一門新的語言,都要從一個一個字開始,所以我們先來了解一下C++中的關鍵字。
在C++98中,有63個關鍵字,如下表:
在這裡插入圖片描述
這裡並不對這些關鍵字一一介紹,在日常的學習中會慢慢接觸到。

2.名稱空間
在C/C++中,完成一個專案時,必定會有大量的變數、函式,而這些變數和函式都是全域性變數,所以難免存在命名衝突,如果為了避免命名衝突就去把變數名(函式名)給的很複雜,就會給之後的維護帶來很大的麻煩。
而名稱空間的出現很好的解決了這個問題,它的關鍵字namespace

,名稱空間實際上就是一個新的作用域,以避免命名衝突或命名汙染。

2.1定義
在定義名稱空間時,首先要有關鍵字namespace,後面跟你要對這段空間起的名字,然後是{}{}裡面的內容就是名稱空間的成員。示例:

  • 普通的名稱空間
namespace  N1
{
	int a = 10;
	int add(int left, int right)
	{
		return left + right;
	}
}
  • 巢狀式名稱空間
namespace N1
{
	int a = 20;
	int sub(int left, int right)
	{
		return left - right;
	}
	namespace N2
	{
		int a = 30;
		int mul(int left, int right)
		{
			return left * right;
		}
	}
}
  • 允許重名的名稱空間
//當發生重名時,編譯器會自動合成多個空間為一個
namespace  N1
{
	int mul(int left, int right)
	{
		return left * right;
	}
}

注:
一個名稱空間在定義完一段作用域以後,這個名稱空間中的所有內容都被侷限於該名稱空間之中。

2.2使用
名稱空間的有三種使用方式:

  • 使用名稱空間名稱及作用域限定符
int main()
{
   printf("%d\n", N::a);
   return 0;
}
  • 使用using引入名稱空間中的成員
using N::a;
int main()
{
	printf("%d\n", a);
	return 0;
}
  • 使用using namespace 名稱空間名稱引入
using namespace N;
int main()
{
	printf("%d\n", a);
	printf("%d\n", b);
	add(10, 25);
	return 0;
}

3.C++中的I/O
在C語言中,有很多的輸入/輸出函式,常用的printfscanf,以及getsputs等等,但在C++中使用cincout,這兩個函式相比C語言中的輸入/輸出函式更為簡便,不需增加資料格式控制。例如:整形–%d,字元–%c。
示例:

#include<iostream>//標頭檔案
using namespace std;//std--標準名稱空間
int main()
{
	int a = 10;
	char b = 'b';
	double c;
	cin >> c;
	cout << a << " " << b << " " << c << endl;
	return 0;
}

顯示結果:
在這裡插入圖片描述

注:
在使用cout標準輸出(控制檯)和cin標準輸入(鍵盤)時,必須包含<iostream >標頭檔案以及std標準名稱空間。

4.預設引數
4.1概念
預設引數是在宣告或定義函式時就為函式的引數指定一個預設值,在呼叫該函式時,如果沒有指定實參則使用該預設值;否則使用指定的實參。
示例:

void fun(int a = 1)
{
	cout<<a<<endl;
}
int main()
{
	fun();//不傳參時列印1
	fun(10);//傳參時列印10
	return 0;
}

4.2分類
1).全預設引數
示例:

void fun(int a=1,int b=2,int c=3)
{
	cout<<a<<endl;
	cout<<b<<endl;
	cout<<c<<endl;
}

2).半預設引數
示例:

void fun(int a,int b=2,int c=3)
{
	cout<<a<<endl;
	cout<<b<<endl;
	cout<<c<<endl;
}

注:

  • 半預設引數的引數必須從右往左依次給出,不能跳躍 ;
  • 預設引數不能在函式宣告和定義中同時出現(如果同時出現,而恰巧宣告和定義提供的值不同時,編譯器會無法確定到底該用那個預設值);
  • 預設值必須是常量或者全域性變數 ;
  • C語言不支援(編譯器不支援);
  • 預設引數可以在宣告中,也可以在定義中,但最好在宣告中給出。

5.函式過載
5.1概念
函式過載是函式的一種特殊情況,C++允許在同一作用域中宣告幾個功能類似的同名函式,這些同名函式的形參列表(引數個數 或 型別 或 順序)必須不同,常用來處理實現功能類似資料型別不同的問題。

示例:

short add(short a, short b)
{
	return a + b;
}
int add(short a, short b)
{
	return a + b;
}

上例屬於函式過載嗎?
顯然,它不屬於函式過載 ,因為兩個函式的引數列表完全相同,不同的只有返回值型別。

注:

  • 在C++中,有四種作用域,分別是全域性、區域性、類域、名稱空間;
  • 函式過載中對返回值型別沒有要求。

5.2名字修飾
名字修飾實際上是一種在編譯過程中,將函式、變數的名稱重新改編的機制。簡單來說就是編譯器為了區分各個函式,將函式通過某種演算法,重新修飾為全域性中唯一存在的名稱。
在C/C++中,一個程式要執行起來,需要經歷以下幾個階段:預處理、編譯、彙編、連結。
在這裡插入圖片描述

C語言的名字修飾規則非常簡單,示例:
在這裡插入圖片描述
上述add函式只給了宣告沒有給定義,因此在連結時就會報錯。

從報錯結果中可以看出,C語言的名字修飾規則只是簡單的在函式名前加下劃線。因此當工程中存在相同函式名的函式時,就會產生衝突。

而C++因為要支援函式過載、名稱空間等,所以其名字修飾規則相對複雜,不同編譯器在底層的實現方式可能都不同。
在這裡插入圖片描述
通過VS中顯示的錯誤可以看出,編譯器實際在底層使用的不是 add 名字,而是被重新修飾過的一個比較複雜的名字,被重新修飾後的名字中包含了:函式的名字以及引數型別。
這就是函式過載中幾個同名函式要求其引數列表必須不同的原因。只要引數列表不同,編譯器在編譯時通過對函式名字進行重新修飾,將引數型別包含在最終的名字中,就可以保證函式名在底層的全域性唯一性。在這裡插入圖片描述
5.3 extern"C"
如果在C++的工程中,需要C風格的函式,只需要在函式名前加上extern"C"即可,這個關鍵字是告訴編譯器將該函式按照C語言規則來編譯。
在這裡插入圖片描述
此時,連結後可以看出是C風格的命名修飾規則。

7.行內函數
7.1概念
inline關鍵字修飾的函式叫做行內函數,在編譯時C++編譯器會在呼叫行內函數的地方展開,並沒有函式壓棧的開銷,所以行內函數的程式執行的效率比較高。
這是沒有inline關鍵字修飾的情況:

int& Add(int a, int b)
{
	int c = a + b;
	return c;
}

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

檢視反彙編:
在這裡插入圖片描述
這裡明顯可以看到呼叫add函式,這就勢必要考慮函式壓棧的開銷,而這樣就會降低效率。
然後再來看一下使用了inline關鍵字以後,它的反彙編程式碼,而要在VS2017中使用inline關鍵字,需要對編譯器做一點修改。如下:
在這裡插入圖片描述
在這裡插入圖片描述
對編譯器修改完以後(在完成了這次操作以後,記得將VS的設定修改回去),再次檢視反彙編:
在這裡插入圖片描述
加了inline關鍵字以後,函式就會變成行內函數,在編譯期間編譯器會用函式體替換函式的呼叫。
7.2特性

  1. inline是一種以空間換時間的做法,省去呼叫函式的開銷,所以當代碼很長或者有迴圈/遞迴的函式不適宜作為行內函數;
  2. inline對於編譯器而言只是一個建議,編譯器會自動優化,如果定義為inline的函式體內有迴圈/遞迴,編譯器在優化時會忽略掉內聯關鍵字。

8.auto關鍵字(C++11)
8.1 auto簡介
auto在之前的C/C++版本中是一個儲存型別的指示符,但在(C++11) 中,它是作為一個新的型別指示符來指示編譯器,auto宣告的變數必須由編譯器在編譯時期推導而得。

int test()
{
	return 5;
}
int main()
{
	int a = 10;
	auto b = a;
	auto c = 'c';
	auto d = 1.2;
	auto e = test();
	auto e;
	return 0;
}

編譯結果如下:
在這裡插入圖片描述
通過這個我們可以知道:在使用auto關鍵字定義變數時必須對其進行初始化,在編譯階段編譯器需要根據初始化表示式來推導auto的實際型別。auto本身並不是一種“型別”,而是在型別宣告時作為一個佔位的作用(比如208中的“0”),編譯器會在編譯期間將auto替換為變數實際的型別。
8.2使用規則
1). auto與指標和引用一起使用
使用auto宣告變數,宣告指標型別是使用autoauto*沒有區別,但宣告引用型別時,必須加上&
2). 在同一行定義多個變數
示例:

int main()
{
	int a = 10;
	auto b = a, c=1.3;
	auto d = 10, e = 20;
	return 0;
}

編譯結果如下:

在這裡插入圖片描述
當想在同一行宣告多個變數時,這些變數必須是相同的型別,否則編譯器會報錯。實際上編譯器只對第一個變數的型別進行推導,然後用推匯出來的型別定義同一行的其他變數。
8.3不能使用的場景

1). auto不能作為函式的引數( 因為編譯器無法對a的實際型別進行推導 )
2). auto不能直接用來宣告陣列
3). 為了避免與C++98中的auto發生混淆,C++11中只保留了auto作為型別指示符的用法
4). auto在實際中常見的優勢用法是和C++11中提供的基於範圍的for迴圈,lambda表示式等進行配合使用
5). auto不能定義類的非靜態成員變數
6). 例項化模板時不能使用auto作為模板引數

9.基於範圍的for迴圈(C++11)
9.1語法
先來看一下C++98中是怎樣來實現for迴圈的?

int main()
{
   int array[] = { 1,2,3,4,5,6 };
   for (int i = 0; i < sizeof(array) / sizeof(int); i++)
   	cout << i << endl;
   return 0;
}
``
而在這段程式碼中,我們花了很多時間去算陣列的大小。而事實上,對於一個有範圍的集合而言,由程式設計師自己來說明迴圈的範圍是多餘的,有時候還會出錯。因此C++11中引入了基於範圍的`for`迴圈。
>`for`迴圈後的括號由冒號“ :”分為兩部分:第一部分是範圍內用於迭代的變數, 第二部分則表示被迭代的範圍。
>
示例:
```c
int main()
{
   int array[] = { 1,2,3,4,5,6 };
   for (auto e : array)
   	cout << e << endl;
   return 0;
}

使用C++11中的迴圈方法後,可以很明顯的發現程式碼量變少了很多。
9.2使用條件

1).for迴圈迭代的範圍必須是確定的
對於陣列而言,就是陣列中第一個元素到最後一個元素的範圍;
對於類而言,應該提供得到 beginend的 方法,beginend就是for迴圈迭代的範圍。
2). 迭代的物件要實現++和==的操作。

10.指標空值(C++11)
10.1 C++98中的指標空值
作為一個優秀的程式猿,宣告一個變數給該變數一個合適的初始值是很有必要的,否則可能會出現難以預計的錯誤,比如未初始化的指標。C++98中我們經常使用NULL來完成這個任務,而NULL實際是一個巨集,不信你看:

int main()
{
	int *p = NULL;
	return 0;
}

NULL轉到定義發現:
在這裡插入圖片描述
可以看到,NULL要麼被定義為字面常量0,要麼被定義為無型別的指標(void*)的常量。不論使用哪一種定義,在使用空值的指標時,都不可避免的會遇到一些麻煩,比如:

void test(int)
{
   cout << test() << endl;
}
void test(int *)
{
   cout << test(int *) << endl;
}
int main()
{
   test(0);
   test(NULL);
   test((int *)NULL);
   return 0;
}

編譯結果如下:
在這裡插入圖片描述
可以看到,不但有一些呼叫過程中出現的錯誤,還有一些語法錯誤,而事實上這些語法錯誤是不存在的。
在C++98中,字面常量0既可以是一個整形數字,也可以是無型別的指標(void*)常量,但是編譯器預設將其看成是一個整形常量,如果要將其按照指標方式來使用,則必須對其進行強轉(void *)0
10.2 nullptrnullptr_t
為了考慮相容性,C++11並沒有消除常量0的二義性,而是給出了全新的nullptr表示空值指標。
C++11之所以不在NULL的基礎上進行擴充套件,是因為NULL本身是一個巨集,而且不同的編譯器對於NULL的實現可能不同,如果直接擴充套件NULL,可能會影響以前的程式。
故此:為了避免混淆,C++11提供了nullptr,即nullptr代表一個指標空值常量。nullptr是有型別的,其型別為nullptr_t,僅僅可以被隱式轉化為指標型別,nullptr_t被定義在標頭檔案:

typedef decltype(nullptr) nullptr_t;

注意:
1). 在使用nullptr表示指標空值時,不需要包含標頭檔案,因為nullptr是C++11作為新關鍵字引入的。
2). 在C++11中,sizeof(nullptr)sizeof((void*)0)所佔的位元組數相同。
3). 為了提高程式碼的健壯性,在後續表示指標空值時建議好使用nullptr

以上,是一些簡單說明,C++天下第一,它的深度遠遠不是一篇部落格就可以說明的,希望大家多讀書多看報,少刷網劇多刷程式碼,謝謝!!!!