C ++入門

c++入門

1.基礎的C++程序編寫

1.1:第一個程序
#include<iostream>

using namespace std;

int main(){
    cout<<"hello world!"<<endl;
    return 0;
}
1.2:C++與C語言第一點區別(輸入,輸出和頭文件)

關於C語言中 #include<stdio,h> 和c++中 #include<iostream> 的異同請查看

https://blog.csdn.net/weixin_36250487/article/details/80293539

1.cin: 表示標準輸入(standard input)的istream類對象。通過 cin 往程序中寫入數據

2.cout : 表示標準輸出(standard output)的ostream類對象。通過cout 從設備輸出或者寫數據。

2: 命名空間

細心的你會發現第一個c++程序中有一句 using namespace std; 了吧。

其原因爲:所謂namespace,是指標識符的各種可見範圍。C++標準程序庫中的所有標識符都被定義於一個名爲std的namespace中。

2.1引入namepase 的目的

解決命名衝突的問題

在C語言中不能出現兩個名字相同的變量或者函數,因此C++引入命名空間的目的就是解決命名空間衝突的問題

#include<iostream>
using namepase std;
namespase A{
    int a=10;
}

namespace B{
    int a=20;
}

int main(){
    cout<<A::a<<endl;
    cout<<B::a<<endl;
    return 0;
}

該程序輸出的兩個a值是不同的,就像這樣引入命名空間後你想要使用那個a值只需要用作用域限定符 :: 告訴編輯器即可。

2.2:命名空間的使用

1.加命名空間名稱及作用域限定符

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

2.使用using 將命名空間中成員引入

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

3.使用using namespace 命名空間名稱引入

using namespce N;
int main()
{
   printf("%d\n", N::a);
   printf("%d\n", b);
   Add(10, 20);
   return 0;
   }

4.其他用法

   namespace N1{
        int a = 10;
        int b = 20;
        int Add(int a, int b){
       //命名空間中可以有函數的存在
            return a + b;
        }
        namespace N2{
    //命名空間中可以出現嵌套式的命名空間 
    //N1::N2::swap(x,y);
            int Swap(int x, int y){
                return x*y;    
            }    
        }    
   }

命名空間總結見:

https://blog.csdn.net/jack_wang128801/article/details/90272870


3.缺省參數

缺省參數是聲明或定義函數時爲函數的參數指定一個默認值在調用該函數時,如果沒有指定實參則採用該默 認值,否則使用指定的實參

void TestFunc(int a = 0)
{
cout<<a<<endl;
}
int main()
{
TestFunc();     // 沒有傳參時,使用參數的默認值
TestFunc(10);   // 傳參時,使用指定的實參
}
3.1全缺省參數
void Date(int year=2019,int month=6,int day=3){
    cout<<"year= "<<year<<endl;
    cout<<"month= "<<month<<endl;
    cout<<"day= "<<day<<endl;
}
3.2半缺省參數
void Date(int year,int month=3,int day=6){}
//【注意】半缺省參數必須從右往左來給出,不可以間隔給出
// 缺省參數不能在函數聲明和定義中同時出現
//缺省值必須是常量或者全局變量

【總結】:所謂的缺省參數是指形參是否被賦予初始值

4.函數重載

4.1 概念:c++允許在同一作用域中聲明幾個功能相似的幾個同名函數,這些函數的形參列表(參數的個數或類型或順序)必須不同。

常用來處理實現功能類似數據類型不同的問題

//函數重載實例
#include<iostream>
#include<windows.h>
using namespace std;
int Add(int a,int b){
    return a + b;
}

double Add(double a, double b){
    //相較第一個而言形參的類型不同
    return a + b;
}

double Add(double a, int b, float c){
    //相較第一個而言形參的類型不同並且有參數個數的不同
    return a + b + c;
}

int main(){
    int a = 10;
    int b = 20;
    double c = 2.0;
    double d = 3.0;
    float f = 3;
    cout<<Add(a,b)<<endl;
    cout << Add(c, d) << endl;
    cout << Add(d, a, f) << endl;
    system("pause");
    return 0;
}
//c語言中函數名相同會提示重定義錯誤

[小結]:構成函數重載時參數需滿足的條件

1.函數參數個數不同

2.參數類型不同

3.參數順序不同

【注】:函數名相同,參數相同.但是返回值類型不同的函數不能構成函數重載


4.2名字修飾

在c/c++中,一個程序要運行需要經歷:預處理、編輯、彙編、鏈接等步驟

5.2.1 預處理:去註釋,宏替換,頭文件展開
5.2.2 彙編:語法檢查,轉換成彙編代碼
5.2.3 編譯:彙編代碼————>機器碼
5.2.4 鏈接:生成可執行文件
5.2.5 在Linux下的修飾

void F1(int a);

——Z2F1I

其中—Z爲前綴,2代表函數名有兩個字符,F1爲函數名,而i(int)爲參數的類型的首字母
void F1(char b);
//_Z2F1C
void F1(int a,char b);
//——Z2F1IC

【小結】:在Linux系統下函數的編譯按一下幾個步驟分塊

1.前綴–z

2.函數名的字符個數

3.函數的名字

4.函數參數列表中參數類型的首字母(所有參數的首字母)


5.2.6 C語言的名字修飾規則非常簡單,只是在函數名字前面加一個下劃線(_函數名),
  • 而在C++文件(xx.cpp)中在函數名字前面加一個 extern "c"既是告訴編輯器該函數按照c語言規則來編譯
extern "c" int Add(int a,int b)
//一個函數的形式
// 若需要全部都用C語言的風格來編輯,形式如下

extern "c"{
    //很多行C語言代碼;
}
  • 函數重載總結
  • 1 C語言不支持,C++支持
  • 2 特點:函數名相同,參數不同(1.類型不同。2.順序不同 3.參數個數不同)
  • 3 C++語言支持重載:函數名修飾規則 name mangling
  • 4 C語言中底層函數名:_ 函數名
  • 5 C++底層函數名: 前綴+函數名+參數類型首字母

6:引用

6.1 引用概念:引用不是重新定義了一個變量,而是給已經存在的變量取個別名,編譯器不會爲引用變量開闢內存空間。它和它引用的變量公用同一塊內存空間
  • 示例代碼如下:
  • 引用格式:類型名& 引用變量名(對象名) = 引用對象
#include<iostream>
#include<windows.h>
using namespace std;

void Testfun(){
    int a = 10;
    int &ra = a;
    //int b=20;
    cout << ra  << endl;
    //結果出現一個10
    cout << ra <<endl<< a<<endl;
    //結果出現兩個10
}


int main(){
    Testfun();
    system("pause");
    return 0;
}
//ra=b 不是引用,表達式含義是將變量 b 的值放到 a
//&ra = b 不是引用,單獨的&ra表示ra取地址
  • 監視結果:&ra與&a的值相同,ra與a值都爲10;

【注意】:定義的引用類型必須與被引用對象的類型相同

如上例中如果出現 double &ra=a; 則會報錯


6.2 引用特性
  • 1.引用在定義時必須初始化
  • 錯誤示例: int &ra;
  • 2.一個變量可以有多個引用
 //正確用法示例:
        int a=10;
        int &ra=a;
        int &rra=a;
        int &rrra=a;
  • 3.引用一旦引用一個實體則不能再引用其他實體
6.3 常引用
 void TestContRef(){
        const int a=10;//常引用
        //int & ra=1;   //該語句編譯時出錯,a爲常量
        const int &ra=a; //常引用
        //int& b=10;    //該語句編譯時出錯,b爲常量
        const int& b=10; //常引用
        double d=12.34;
        const int& rd=d;//常引用(rd指向的是隱式類型轉換時生成的臨時變量)
        int c=d; //隱式類型轉換--->臨時變量具有常性
        //int& rd=d;     //該語句編譯時出錯,類型不同    
    } 
6.4 使用場景
  • 1. 做參數
  • 傳指針和傳引用效果一樣;

示例代碼:

#include<iostream>
#include<windows.h>
 
using namespace std;
 
void Swap(int *pa,int *pb){
    int tmp = *pa;
    *pa = *pb;
    *pb = tmp;
}

void Swap(int &ra,int &rb){
    int tmp = ra;
    ra = rb;
    rb = tmp;
}
//上面的兩個函數修改的是你上傳的參數它所指向的空間的值
#include<iostream>
#include<stdlib.h>

using  namespace std;

void swap(int *pa, int *pb){
	int temp=*pa; //pa的地址與主函數中x地址相同
	*pa = *pb;   //pb的地址與主函數中x地址相同
	*pb = temp;
	//交換的是兩個參數原來地址空間中的值,
	//既原函數中定義的變量x,y的地址空間中的值被交換了
}

//void swap(int &a, int &b){
//	int temp = a;
//	a = b;
//	b = temp;
//   //交換的是兩個參數原來地址空間中的值,
//   //既原函數中定義的變量x,y的地址空間中的值被交換了
//}


void  swap(int a, int b){
	int temp = a;
	a = b;
	b = temp;
	//交換的是臨時拷貝創建的空間裏的值
	//其原地址空間內的值並沒有被交換 
}
int main(){
	int x = 10;
	int y = 20;
	swap(&x, &y);
	swap(x, y);
	system("pause");
	return 0;
}

【小結】:傳指針與傳引用的效果是一樣的(都是與原來的變量的地址空間打交道),而傳值時會發生臨時拷貝(代價太大),所以相比較而言傳指針和傳引用的效率比較高

  • 2.做返回值
   #include<iostream>
   #include<windows.h>

  using namespace std;
        int& Testfuc(int &a){
            a+=10;
            return 0;
        }
          int main(){
          int a = 10;
          int &ra=Testfuc(a);
          system("pause");
          return 0;
     }
//此程序中的&ra和&a都指向變量a的地址

【注意】:如果函數返回時,離開函數作用域後,其棧上空間已經還給了系統,因此不能用函數棧幀上的空間做爲引用類型的返回值,如果函數類型返回,返回值的生命週期必須不受函數的限制(既函數週期比較長)


#include<iostream>
#include<windows.h>
#include<time.h>

using namespace std;

int main(){
    size_t beginl = clock();
    for (int i = 0; i < 10000; i++){
      cout << "i=" << i << endl;
    }
    size_t endl = clock();
    cout << (endl- beginl) /CLOCKS_PER_SEC<< endl;
        system("pause");
    return 0;
}

//以毫秒的形式輸出循環的執行時間
//引用傳值的效率高於值拷貝的形式
6.6 指針與引用的區別
  • 引用語法層面:和指針指向同一塊內存空間,引用本身沒有開闢新的空間
  • 引用底層實現:引用開闢新的空間,和指針的的實現相同
int a=10;                               int a=10;

mov  dword ptr [a], OAh                  mov      dword ptr [a], 0Ah

 

int& ra = a;                             int* pa = &a;

lea       eax, [a]                       lea  еах,[a]

 mov      dword ptr [ra], eax            mov  dword ptr [pa], eax

 //指針的解引用和引用的解引用底層實現時一樣的

ra = 20;                                 *pa = 20;

mov       eax, dword ptr [ra]             mov    eax, dword ptr[ра]

mov       dword ptr [eax], 14h            mov    dword ptr [eax], 14h 

引用和指針的不同點:

6.6.1. 引用在定義時必須初始化,指針沒有要求

6.6.2. 引用在初始化時引用一個實體後,就不能再引用其他實體,而指針可以在任何時候指向任何一個同類型實體

6.6.3. 沒有NULL引用,但有NULL指針

*6.6.4. 在sizeof中含義不同:引用結果爲引用類型的大小,但指針始終是地址空間所佔字節個數(32位平臺下佔4個字節)cout << sizeof(a)<<endl; //結果爲4

6.6.5. 引用自加即引用的實體增加1,指針自加即指針向後偏移一個類型的大小

6.6.6. 有多級指針,但是沒有多級引用

6.6.7. 訪問實體方式不同,指針需要顯式解引用,引用編譯器自己處理

**6.6.8. 引用比指針使用起來相對更安全 ** (引用使用時必須初始化)


7.內聯函數

7.1 概念:以inline修飾的函數叫做內聯函數,編譯時C++編譯器會在調用內聯函數的地方展開,沒有函數壓棧的開銷,內聯函數提升程序運行的效率。
inline Add(int a,int b){
    return a+b;
}
int main(){
    int ret=Add(1,3) ------->//若是內斂函數的話,函數體直接展開成int ret=1+3;
        //如果不是內聯函數的話,底層實現會調用add的函數棧幀call add(0X001)
    return 0;
}
對於普通函數和內聯函數的比較
#include<iostream>
using namespace std;
int add(int a,int b){
    return a+b;
}

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

int main(){
    int ret=add(1,3)
    return 0;
}

普通函數

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

底層實現:普通函數,編譯時調用add的函數棧幀

push 3

push 1

call add(0821042h)

內聯函數

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

底層實現:內聯函數,編譯時函數體展開

mov eax ,2

00012220 add

00012223 mov dword ptr[ret] ,eax

7.2 inline函數的特性
  1. inline是一種以空間換時間的做法,省去調用函數額開銷。所以代碼很長或者有循環/遞歸的函數不適宜使用內聯函數。
  2. inline對於編譯器而言只是一個建議,編譯器會自動優化,如果定義爲inline的函數體內有循環/遞歸等,編譯器優化時會忽略掉內聯。
  3. inline不建議聲明和定義分離,分離會導致鏈接錯誤。因爲inline被展開,就沒有函數地址了,鏈接就會找不到

面試題:
  • 【面試題】宏的優缺點?
優點:
  • 1.增強代碼的複用性。
  • 2.提高性能。
缺點:
  • 1.不方便調試宏。(因爲預編譯階段進行了替換)
  • 2.導致代碼可讀性差,可維護性差,容易誤用。
  • 3.沒有類型安全的檢查 。
C++有哪些技術替代宏
  • 1. 常量定義 換用const
  • 2. 函數定義 換用內聯函數

8.關鍵字auto

  • 特點:使用auto定義變量時必須對其進行初始化,在編譯階段編譯器需要根據初始化
  • 表達式來推導auto的實際類型。因此auto並非是一種“類型”的聲明,而是一個類型聲明時的“佔位符”,編譯器在編譯期會將auto替換爲變量實際的類型。
#include<iostream>
#include<windows.h>
 
using namespace std;
int main(){
    int a = 10;
    auto b = a;
    double c = 20.3;
    auto d = c;
    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    cout << typeid(d).name() << endl;
    system("pause");
    return 0;
}
//顯示出了類型的名稱
8.2.1. auto與指針和引用結合起來使用用auto聲明指針類型時,用auto和auto*沒有任何區別,但用auto聲明引用類型時則必須加&
int main()
{
    int x = 10;
    auto a = &x;  //定義了一個引用
    auto* b = &x;  //(int *)
    auto& c = x;    //推導出引用變量的類型
    cout << typeid(a).name() << endl;
    cout << typeid(b).name() << endl;
    cout << typeid(c).name() << endl;
    *a = 20;
    *b = 30;
     c = 40;
    return 0;
}
8.2.2 在同一行定義多個變量
  • 當在同一行聲明多個變量時,這些變量必須是相同的類型,否則編譯器將會報錯,
  • 爲編譯器實際只對第一個類型進行推導,然後用推導出來的類型定義其他變量。
void TestAuto()
{
    auto a = 1, b = 2; 
    auto c = 3, d = 4.0;  // 該行代碼會編譯失敗,因爲c和d的初始化表達式類型不同
}
  • 若同一個程序中同時定義了多個 auto ,其會根據第一個變量表達式去推導類型。
8.3 auto不能推導的場景。
8.3.1 auto不能做函數的參數
// 此處代碼編譯失敗,auto不能作爲形參類型,因爲編譯器無法對a的實際類型進行推導
void TestAuto(auto a)
{}

8.3.2 auto不能直接用來聲明數組
void TestAuto()
{
    int a[] = {1,2,3};
    auto b[] = {456};
}

8.3.3爲了避免與C++98中的auto發生混淆,C++11只保留了auto作爲類型指示符的用法
8.3.4 auto在實際中最常見的優勢用法就是跟以後會講到的C++11提供的新式for循環,還有lambda表達式等進

行配合使用。

8.3.5 auto不能定義類的非靜態成員變量(暫不做講解,後面講)
8.3.6實例化模板時不能使用auto作爲模板參數(暫不做講解,後面講)

9. 基於範圍的for循環(C++11)

9.1 for循環後的括號由冒號“ :”分爲兩部分:第一部分是範圍內用於迭代的變量,第二部分則表示被迭代的範圍。
#include<iostream>
#include<windows.h>

using namespace std;

int main(){
    int arr[] = {1,2,3,4,5,6,7,8,9};
    int size = sizeof(arr) / sizeof(arr[0]);
    int i = 0;
    for (; i < size; i++){
        cout << "arr1[i]=" << arr[i] << endl;
    }
    cout << endl;
    for (int e : arr){
        cout << "arr2[i]=" << e << endl;
    }
    cout << endl;
    for (auto& e : arr){
        cout << "arr3[i]=" <<e << endl;
    }
    system("pause");
    return 0;
}

【注意】:與普通循環類似,基於範圍的for循環可以用continue來結束本次循環,也可以用break來跳出整個循環

9.2 範圍for的使用條件
1. for循環迭代的範圍必須是確定的
  • 對於數組而言,就是數組中第一個元素和最後一個元素的範圍;對於類而言,應該提
  • 供begin和end的方法,begin和end就是for循環迭代的範圍。
  • 注意:以下代碼就有問題,因爲for的範圍不確定
void TestFor(int array[]) 
{ 
 for(auto& e : array) 
 cout<< e <<endl; 
}

10.指針空值nullptr(C++11)

#include<iostream>
#include<windows.h>

using namespace std; 

int main(){
    int *p = NULL;//c++中此處的NULL被定義成常數 "0"
    int *pp = nullptr;
    cout << typeid(pp).name() << endl;
    cout << typeid(nullptr).name() << endl;
    system("pause");
    return 0;
}
結果如下: 
//int *
//std::nullptr_t

程序本意是想通過f(NULL)調用指針版本的f(int)函數,但是由於NULL被定義成 0,因此與程序的初衷相悖。在C++98中,字面常量0既可以是一個整形數字,也可以是無類型的指針(void)常量,但是編譯器默認情況下將其看成是一個整形常量,如果要將其按照指針方式來使用,必須對其進行強轉(void*)0。**

10.2 nullptr 與 nullptr_t

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。

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