1.c++基礎(chǔ)(1)


2.變量和基本類型

c++的算術(shù)類型:

類 型 含 義 最小尺寸
bool 布爾類型 未定義
char 字符 8位
wchar_t 寬字符 16位
char16_t Unicode字符 16位
char32_t Unicode字符 32位
short 短整型 16位
int 整型 16位
long 長整型 32位
long long 長整型 64位
float 單精度浮點(diǎn)數(shù) 6位有效數(shù)字
double 雙精度浮點(diǎn)數(shù) 10位有效數(shù)字
long double 擴(kuò)展精度浮點(diǎn)數(shù) 10位有效數(shù)字

一個(gè)char和一個(gè)機(jī)器字節(jié)一樣,一個(gè)int至少和一個(gè)short一樣大,一個(gè)long至少和一個(gè)int一樣大。

通常float以一個(gè)(32 bit)表示,double以2個(gè)字來表示,分別有7個(gè)和16個(gè)有效位。

8位unsigned char 可以表示0到255的值,signed char可以表示-128 到 127。

  • 多數(shù)平臺(tái)上int類型占32位,取值范圍為-2147483648 至 2147483647

類型轉(zhuǎn)換

bool b = 42;           // b = true
int i = b;             // i = 1
i = 3.14;              // i = 3
double pi = i;         // pi = 3.0
unsigned char c = -1;  // 若char占8bit,則c = 255
signed char c2 = 256;  // 若char占8bit,c2的值是未定義的
  • 把非bool類型的算術(shù)值給bool類型時(shí),初始為0則結(jié)果為false,否則結(jié)果為true
  • 把bool類型賦值給非bool類型時(shí),初始值為false則結(jié)果為0,初始值為true則結(jié)果為1
  • 把浮點(diǎn)數(shù)賦值給整型時(shí),只保留整數(shù)部分
  • 整數(shù)給浮點(diǎn)數(shù)時(shí),小數(shù)部分記0
  • 給無符號(hào)類型一個(gè)超出其表示范圍的值時(shí),結(jié)果的初始值對(duì)無符號(hào)類型表示數(shù)值總數(shù)取模后的余數(shù),例如給8bit的unsigned char(可以表示0到255)賦值-1,結(jié)果為-1%256=255
  • 給帶符號(hào)的類型賦值一個(gè)超出它表示范圍的值時(shí),結(jié)果是未定義(undefined)的,可能會(huì)出錯(cuò)

注意! 帶符號(hào)數(shù)與無符號(hào)數(shù)運(yùn)算時(shí),會(huì)被轉(zhuǎn)化為unsigned,int a = -1可能會(huì)變成4294967295,切勿混用unsigned類型和signed類型!

字面值

O開頭的數(shù)代表八進(jìn)制數(shù),0x開頭的數(shù)表示十六進(jìn)制,例如整數(shù)20:

20   // 十進(jìn)制
O24  // 八進(jìn)制
0x14 // 十六進(jìn)制

一般復(fù)數(shù)的那個(gè)負(fù)號(hào)不會(huì)存儲(chǔ)在字面值內(nèi),作用僅僅是對(duì)字面值取負(fù)

轉(zhuǎn)義序列:

:-:|:-:|:-:
換行符 \n| 橫向制表符 \t |報(bào)警符 \a
縱向制表符 \v| 退格符 \b| 雙引號(hào) "
反斜線\ |問號(hào) ? |單引號(hào)'
回車符 \r |進(jìn)紙符 \f|

也可以通過添加前后綴來指定字面值的類型:

對(duì)于字符有:

前綴 含義 類型
u Unicode 16字符 char16_t
U Unicode 32字符 char32_t
L 寬字符 wchar_t
u8 UTF-8(僅用于字符串字面值常量) char

對(duì)于整型和浮點(diǎn)型有:

后綴 類型
u or U unsigned
l or L long
ll or LL long long
f or F float
l or L long double

變量

變量定義的基本形式:

類型說明符 + 一個(gè)或多個(gè)變量名組成的列表(以逗號(hào)分隔,分號(hào)結(jié)束)

  • 何為對(duì)象

    對(duì)象(object)是指一塊能存儲(chǔ)數(shù)據(jù)并具有某種類型的內(nèi)存空間

  • 對(duì)象初始化

    當(dāng)對(duì)象在創(chuàng)建時(shí)獲得了一個(gè)特定的值,那么這個(gè)對(duì)象被初始化了,在同一條語句中可以用先定義的變量值去初始化后定義的變量。

      double price = 100.1, discount = price * 0.16;
    

    注意初始化不是賦值!初始化=創(chuàng)建變量的同時(shí)賦予其一個(gè)初值,賦值=擦除對(duì)象的當(dāng)前值以一個(gè)新的值來代替。

  • 默認(rèn)初始化

    變量將被初始化為默認(rèn)值,也有可能定義在函數(shù)體內(nèi)部的內(nèi)置類型變量不被初始化

    建議初始化每一個(gè)內(nèi)置類型的變量!

聲明和定義的關(guān)系 *

c++采用分離式編譯機(jī)制,允許將程序分割為若干個(gè)文件,每個(gè)文件可以被獨(dú)立編譯

為了支持分離式編譯,c++將聲明和定義區(qū)分開

聲明(declaration)使名字為程序所知

定義(definition)負(fù)責(zé)創(chuàng)建與名字關(guān)聯(lián)的實(shí)體

變量聲明規(guī)定了變量的類型和名字,定義在這方面也與之相同。但是定義還申請(qǐng)存儲(chǔ)空間,也可能會(huì)為變量賦一個(gè)初值。

如果想聲明一個(gè)變量而非定義它,需要用到extern關(guān)鍵字,而且不要顯示初始化變量。任何包含了顯示初始化的聲明即成為定義!并且變量只能被定義一次,可以被多次聲明。

extern int i;     //聲明 i 而非定義 i
int j;            //聲明并定義 j
extern int k = 1;//定義 k

!如果要在多個(gè)文件中使用同一個(gè)變量,就必須把聲明和定義分離。此時(shí),變量的定義必須出現(xiàn)在且只能出現(xiàn)在一個(gè)文件中,而其他用到該變量的文件必須對(duì)其進(jìn)行聲明,不能重復(fù)定義。

  • c++是一種靜態(tài)類型語言,其含義是在編譯階段檢查類型。其中檢查類型的過程稱為類型檢查。我們?cè)谑褂媚硞€(gè)變量前必須聲明其類型。

標(biāo)識(shí)符

由字母、數(shù)字、下劃線組成。必須以字母和下劃線開頭。

int somename, someName, SomeName, SOMENAME;

并且還有一些保留字。用戶自定義的標(biāo)識(shí)符中不能出現(xiàn)連續(xù)兩個(gè)下劃線,也不能以下劃線加大寫字母開頭。定義在函數(shù)體外的標(biāo)識(shí)符不能以下劃線開頭。

int _ = 1; //是正確的

名字的作用域

作用域以花括號(hào)分隔。

定義在函數(shù)體之外的名字具有全局作用域,例如main。

相對(duì)的塊作用域

復(fù)合類型

基本數(shù)據(jù)類型 + 類型修飾符 + 變量名

int i = 1024, *p = &i, &r = i;

最好把類型修飾符和變量名寫在一起

  • 引用(左值引用)

    引用即別名, 引用必須被初始化

  • 指針

    定義多個(gè)指針時(shí),必須每個(gè)變量前面都有符號(hào) *

    指針存放某個(gè)對(duì)象的地址,要想獲取該地址,要使用取地址符(&)

      double dval;
      double *pd = &dval;
      double *pd2 = pd;
    
      int *pi = pd; //錯(cuò)誤,指針pi的類型和pd不匹配
      pi = &dval;   //錯(cuò)誤,指針pi的類型和dval不匹配
    

    通過解引用符(*)來得到指針?biāo)傅膶?duì)象。

    !對(duì)于符號(hào)(*, &),在定義時(shí),作為聲明的一部分;同時(shí)也可以作為解引用符和取地址符。

    生成一個(gè)空指針

      int *p1 = nullptr;
      int *p2 = 0;
      int *p3 = NULL;
    

    建議初始化所有指針!

    把指針用于條件表達(dá)式時(shí),任何非0指針的條件值都是true。

  • void * 指針

    可以用于存放任意對(duì)象的地址,但也只能存儲(chǔ)地址,不能訪問內(nèi)存空間中的對(duì)象。

  • 指向指針的指針

    可以有指向指針的指針的...指針

  • 指向指針的引用

    相當(dāng)于對(duì)指針的引用

      int i = 42;
      int *p;
      int *&r = p;   // r 是對(duì)指針 p 的引用
    

    要理解 *&r 到底是什么,應(yīng)該從右往左看類型修飾符,距離r最近的符號(hào)是&,那么r就是一個(gè)引用。其余的符號(hào)確定r引用的類型是什么,符號(hào)*說明r引用的是一個(gè)指針。最后int類型說明r引用的是一個(gè)int類型的指針。

    從右往左看,很重要!

const限定符

const類型的對(duì)象一旦創(chuàng)建后其值就不能再改變,所以const對(duì)象必須初始化。

可以用const對(duì)象去初始化其他對(duì)象,因?yàn)槌跏蓟僮鞑粫?huì)改變本身的值,只是拷貝了值。

int i = 42;
const int ci = i;
int j = ci;

! 默認(rèn)狀態(tài)下const對(duì)象僅在文件內(nèi)有效,因?yàn)樵诰幾g過程中,會(huì)把會(huì)用到該變量的地方都替換成對(duì)應(yīng)的值。

當(dāng)在多個(gè)文件中,出現(xiàn)同名的const變量時(shí),等同于在不同文件中分別定義了不同的變量,都需要進(jìn)行初始化。

如果在多個(gè)文件中,都要用到同一個(gè)值的const變量,而且這一變量的初始值不是一個(gè)常量表達(dá)式,又確實(shí)有必要在文件間共享。一個(gè)解決辦法就是,對(duì)于 const 變量不管聲明還是定義都添加 extern 關(guān)鍵字,這樣就只需要定義一次就夠了。

// file_1.cc中定義并初始化一個(gè)常量
extern cosnt int bufSize = fcn();
// file_1.h頭文件
extern cosnt int bufSize;

const的引用

稱為對(duì)常量的引用,也不能修改它所綁定的對(duì)象。

const int ci = 100;
const int &r1 = ci;  //正確
r1 = 42;             //錯(cuò)誤,不能修改const對(duì)象
int &r2 = ci;        //錯(cuò)誤,試圖讓一個(gè)非常量引用指向一個(gè)常量對(duì)象

嚴(yán)格來講并不存在常量引用。

對(duì)const的引用可能引用一個(gè)并非const的對(duì)象。

int i = 42;
int &r1 = i;
const int &r2 = i; //r2綁定對(duì)象i,但是不允許通過r2修改i的值
r1 = 0;   // 通過r1改變了i的值,同時(shí)會(huì)引起r2的改變

r2 綁定非常量整數(shù) i 是合法的行為。

指針和const

要想存放常量對(duì)象的地址,只能用指向常量的指針,也不能賦值。

const double pi = 3.14159;
const double *ptr = π

同時(shí)指向常量的指針也能指向一個(gè)非常量。所謂指向常量的指針,只是不能通過這個(gè)指針來改變對(duì)象的值,但是沒有規(guī)定那個(gè)對(duì)象的值不能通過其他途徑改變。

const指針

允許把指針本身定為常量,常量指針必須初始化,而且一旦初始化完成它的值(存放的地址)就不能再改變了。

int errNumb = 0;
int *const curErr = &errNumb;   //curErr一直指向errNumb
const double pi = 3.14159;
const double *const pip = *pi;  //指向常量對(duì)象的一個(gè)常量指針

指針本身是個(gè)常量,可以改變它指向的對(duì)象的值!

頂層const

頂層const 表示指針本身是個(gè)常量

底層const 表示指針?biāo)傅膶?duì)象是個(gè)常量

指針也可以同時(shí)是底層和頂層const

int i = 0;
int *const p1 = &i;       //p1的值不能改變,頂層const
const int ci = 42;        //ci的值不能改變,頂層const
const int *p2 = &ci;      //p2的值可以改變,底層const
const int *const p3 = p2; //靠右的const是頂層,靠左的cosnt是底層
const int &r = ci;        //用于聲明引用的const都是底層cosnt

在執(zhí)行拷貝操作時(shí),頂層cosnt不受影響。

另一方面,拷入和拷出的對(duì)象必須具有相同的底層const資格,或兩個(gè)對(duì)象的數(shù)據(jù)類型必須能夠轉(zhuǎn)換(一般非常量可以轉(zhuǎn)換成常量)

就是說可以用一個(gè) const int & 去綁定一個(gè)普通的int對(duì)象

類型別名

兩種方法定義類型別名:

typedef double wages;    //wages = double
typedef wages base, *p;  //base = double, p = double*

using SI = Sales_item;   //SI = Sales_item

注意不是簡單的替換:

typedef char *pstring;
const pstring cstr = 0;  //cstr是指向char的**常量指針**
const pstring *ps;       //ps是一個(gè)指針,它指向的對(duì)象是指向char的常量指針

但是上述語句不是簡單的替換就行的,不等于以下語句:

const char *cstr = 0;    //這是指向常量字符的指針,和上面的不同?。?!

decltype

類型說明符decltype,作用是返回操作數(shù)的數(shù)據(jù)類型(包括頂層const和引用在內(nèi))

const int ci = 0, &cj = ci;
decltype(ci) x = 0;       //正確,x是一個(gè)const int類型
decltype(cj) y = x;       //正確,y是一個(gè)const int&,綁定到x
decltype(cj) z;           //錯(cuò)誤,z是一個(gè)const int&,必須初始化

注意decltype對(duì)括號(hào)很敏感,如果decltype使用的是一個(gè)不加括號(hào)的變量,得到的結(jié)果就是該變量的類型。如果給變量加上了一層括號(hào),這個(gè)變量就變成了一個(gè)表達(dá)式,表達(dá)式是可以作為賦值語句左值的,因此是一個(gè)引用類型。

int i = 0;
decltype((i)) d;   //d是int&,必須初始化,報(bào)錯(cuò)
decltype(i) e;     //e是int類型

?。?!雙層括號(hào) decltype((i)) 得到的結(jié)果永遠(yuǎn)是一個(gè)引用,一層括號(hào)只有當(dāng)里面是引用的時(shí)候才是引用。

預(yù)處理器

  • 一項(xiàng)預(yù)處理功能 #include,當(dāng)看到 #include 標(biāo)記時(shí),會(huì)用指定的頭文件內(nèi)容代替它。

  • 頭文件保護(hù)符依賴于預(yù)處理變量,#define 把一個(gè)名字設(shè)定為預(yù)處理變量,#ifdef, #ifndef分別當(dāng)且僅當(dāng)變量已定義時(shí)、未定義時(shí)為真,這兩個(gè)if直到#endif 指令為止。

      #ifndef SALES_DATA_H
      #define SALES_DATA_H
      #include <string>
      struct Sales_data{
          ...;
      }
      #endif
    

??!預(yù)處理變量無視作用域規(guī)則





3.字符串、向量和數(shù)組

命名空間

使用using聲明

using namespace::name;  
//例如 using std::cin;  
      using std::endl;

作用域操作符 :: ,含義是編譯器從操作符左側(cè)名字所示的作用域中尋找右側(cè)的那個(gè)名字。例如 std::cin 就是使用命名空間 std 中的名字 cin 。

一般來說,頭文件不應(yīng)該包含 using 聲明

標(biāo)準(zhǔn)庫類型string

#include<string>

定義和初始化:

string s1;             //默認(rèn)初始化,s1是一個(gè)空串
string s2 = s1;        //兩種拷貝初始化
string s22(s1);
string s3 = "hiya";    //s3是該字符串字面值的副本
string s33("hiya");
string s4(10, 'c');    //s4是10個(gè)字符c組成的字符串

string的操作:

os<<s               //流操作,將s寫到輸出流os中,返回os
is>>s               //從is中讀取字符串賦給s,字符串以空白分隔,返回is
getline(is, s)      //從is中讀取一行賦給s,返回is
s.empty()
s.size()            //返回字符的個(gè)數(shù)!!!!用size
s[n]                //返回第n個(gè)字符的引用
s1 + s2             //返回s1與s2連接后的結(jié)果
s1 = s2
s1 == s2
s1 != s2
<, >, <=, >=

如果使用 cin>>str 時(shí),會(huì)自動(dòng)忽略開頭的空白(空格,換行,制表符等),從第一個(gè)真正字符開始,直到遇見下一個(gè)空白為止。?。?strong>以空格為分界?。?/p>

例如輸入" Hello world ",會(huì)得到"Hello"

讀取未知數(shù)量的string對(duì)象:

string word;
while(cin >> word){
    cout << word << endl;
}

讀取一整行:

string line;
while(getline(cin, line)){
    cout << line << endl;
}

getline 得到的一行中不包括換行符,得手動(dòng)加上 endl。 觸發(fā)getline函數(shù)返回的那個(gè)換行符實(shí)際被丟掉了。

  • size()函數(shù)返回的是一個(gè) size_type 類型,無符號(hào)數(shù)!

    ?。。∽⒁?,如果表達(dá)式中已經(jīng)有了 size() 類型,就不要用帶符號(hào)的 int 類型了,否則會(huì)轉(zhuǎn)化成無符號(hào)數(shù)產(chǎn)生意想不到的后果。

    或者可以:

      auto len = line.size(); 
    

string 的大小比較是對(duì)字典序進(jìn)行比較,對(duì)大小寫敏感,'A' = 65, 'a' = 97

當(dāng)把字符串和字面值相加時(shí),必須保證每個(gè) '+' 兩側(cè)至少有一個(gè)是 string 類型! 字符串字面值string 是兩種不同的類型?。?!

  • 處理 string 中的字符

    使用cctype頭文件

      isalnum(c) //是字母或數(shù)字
      isalpha(c) //是字母
      iscntrl(c) //是控制字符
      isdigit(c) //是數(shù)字
      islower(c) //是小寫字母
      isupper(c) //是大寫字母
      ispunct(c) //是標(biāo)點(diǎn)符號(hào)
      ...
      tolower(c)
      toupper(c)
    
  • c++ 版本的C標(biāo)準(zhǔn)庫頭文件

    c語言的頭文件形如 name.h ,而c++則將這些名字去掉了 .h ,相應(yīng)的增加了 c 字母前綴,變成 cname 。 例如:

      #include <cmath>
      #include <cctype>
    
  • 處理字符

    c++11 新的范圍for語句:

      for (declaration : expression)
          statement;
    
      for (auto c : str)
          cout<< c <<endl;
    
      for (auto &c : str)  // 如果要修改字符,必須要引用
          c = toupper(c);
    

    下標(biāo)操作([ ]):

    第一個(gè)字符 s[0] , 最后一個(gè)字符 s[s.size() - 1]

    e.g. 把字符串 s 的第一個(gè)詞改成大寫形式:

      for(decltype(s.size()) index = 0; index != s.size() && !isspace(s[index]); ++index)
          s[index] = toupper(s[index]);
    
      //s = "some string", 結(jié)果s = "SOME string"
    
  • reverse() 函數(shù)

    c++中, reverse() 函數(shù)是在 <algorithm> 中定義的,用于反轉(zhuǎn)在 [first,last) 范圍內(nèi)的順序。

    可以用于反轉(zhuǎn) vector 類型,或者 string 類型等。

      vector<int> v={1,2,3,4,5};
      reverse(v.begin(),v.end());//v的值為5,4,3,2,1
    
      string str="C++REVERSE";
      reverse(str.begin(),str.end());//str結(jié)果為ESREVER++C
    

標(biāo)準(zhǔn)庫類型 vector

vector(向量),有時(shí)也被稱為容器(container)。vector 實(shí)際是一個(gè)類模板,編譯器根據(jù)模板創(chuàng)建類或者函數(shù)的過程稱為實(shí)例化,通過在模板名字后加上尖括號(hào),在尖括號(hào)內(nèi)填放信息。

vector<int> ivec;
vector<Sales_item> Sales_vec;
vector<vector<string>> file; // 在早期版本中,要在后面的兩個(gè)>>中加上一個(gè)空格,寫成下列形式:
vector<vector<string> > file;

初始化:

vector<T> v1;
vector<T> v2(v1);
vector<T> v2 = v1;
vector<T> v3(n, val);
vector<T> v4(n); // v4包含了n個(gè)重復(fù)地執(zhí)行了值初始化的對(duì)象
vector<T> v5{a, b, c...};  //列表初始化
vector<T> v5 = {a, b, c...};  //效果同上,這是c++11的新標(biāo)準(zhǔn)

可以進(jìn)行默認(rèn)初始化,不含任何元素。

在進(jìn)行值初始化時(shí),可以不加值具體是什么,只寫入數(shù)字,例如 vector<T> v4(n)

當(dāng)使用的模板是 int 時(shí),元素初值自動(dòng)設(shè)為0。

??!注意使用花括號(hào)和圓括號(hào),這兩個(gè)影響非常大?;ɡㄌ?hào)是進(jìn)行列表初始化,圓括號(hào)是按提供的 (n, val) 來構(gòu)造 vector 對(duì)象。

  • 添加元素

    push_back(..) 把元素添加到尾端

    ??!注意,雖然vector對(duì)象能高效增長,定義vector對(duì)象的時(shí)候設(shè)定其大小也就沒什么必要了,但事實(shí)上這么做性能可能更差!

    在創(chuàng)建vector時(shí),預(yù)先設(shè)定其容量是最好的。

    ??! 范圍for語句體內(nèi)不應(yīng)改變其所遍歷的序列的大小,不能在里面對(duì)vector進(jìn)行添加元素等操作。

  • 對(duì)vector的操作

      v.empty()
      v.size()   // 返回vector中的元素個(gè)數(shù),size和empty操作和string類的一樣
      v.push_back(t)
      v[n]
      v1 = v2 
      v1 = {a, b, c, ...}   //同列表中的元素拷貝替換v1中的元素
      v1 == v2
      v1 != v2
      <, <=, >, >=     //按**字典序**進(jìn)行比較
    

    size() 返回的是由 vector 定義的 size_type 類型,注意要使用

      vector<int>::size_type  // 正確
      vector::size_type       // 錯(cuò)誤,要指定類型
    

    只能對(duì)確知已存在的元素執(zhí)行下標(biāo)操作!

迭代器

所有標(biāo)準(zhǔn)庫容器都可以使用迭代器訪問,但是只有少數(shù)才同時(shí)支持下標(biāo)運(yùn)算符。string和vector都同時(shí)支持這兩種訪問方式。

嚴(yán)格來講string不屬于容器類型,但是很多容器類型的操作string都支持。

獲取迭代器使用 begin 和 end 成員。 其中 begin 指向第一個(gè)元素,end 表示尾元素的下一個(gè)元素的位置,如果為空,則有 begin == end。 end 稱為尾后迭代器,簡稱尾迭代器。

  • 迭代器的運(yùn)算符(類似于指針):

      *iter
      iter->mem   //解引用 iter 并獲取該元素的名為 mem 的成員,等價(jià)于 (*iter).mem
      ++iter
      --iter
      iter1 == iter2
      iter1 != iter2
    

    盡量習(xí)慣使用迭代器以及!=運(yùn)算符,不是所有的標(biāo)準(zhǔn)庫容器都支持<運(yùn)算的,但是都支持迭代器和!=操作。

  • 迭代器類型:

    那些擁有迭代器的標(biāo)準(zhǔn)庫類型使用 iterator 和 const_iterator 來表示迭代器的類型。

      vector<int>::iterator it;
      string::iterator it2;
          
      vector<int>::const_iterator it3;  // it3 只能讀取元素,不能寫元素
      string::const_iterator it4;       // it4 只能讀取元素,不能寫元素
    

    const_iterator 類型和常量指針差不多,能讀取但不能修改它所指的元素值。

    如果 vector 對(duì)象或 string 對(duì)象是一個(gè)常量,只能使用 const_iterator 。

    ?。?對(duì)于常量的容器類型,begin 和 end 運(yùn)算符得到的是一個(gè) const_iterator ,要用對(duì)應(yīng)的 const_iterator 接住。如果一定要從非常量容器中得到常量迭代器,可以使用 cbegincend 這兩個(gè)運(yùn)算符。

  • 箭頭運(yùn)算符 ->

    it -> mem 的含義是 (*it).mem 包含了對(duì) it 進(jìn)行解引用,以及點(diǎn)運(yùn)算符。

    例如一個(gè) vector<string> 類型的 text ,用迭代器訪問遍歷 text,直到遇到空白字符串為止:

      for(auto it = text.cbegin(); it != text.cend() && !it -> empty(); ++it)
          cout << *it << endl;
    

    其中 !it -> empty() 等價(jià)于 !(*it).empty()。先解引用得到 string 類型,再判斷這個(gè)字符串是否為空。

??! 但凡使用了迭代器的循環(huán)體,都不要向迭代器所屬的容器添加元素。

  • string 和 vector 的迭代器的其他運(yùn)算

      iter + n
      iter - n
      iter1 += n
      iter1 -= n
      iter1 - iter2   //相減得到迭代器之間的距離
      >, >=, <, <=
    

    也可以進(jìn)行一些算術(shù)運(yùn)算,例如得到中間元素:

      auto mid = vi.begin() + vi.size() / 2;
    

    這個(gè)距離是一個(gè) difference_type ,帶符號(hào)整數(shù),因?yàn)榫嚯x可正可負(fù)。

數(shù)組

數(shù)組的大小確定不變

數(shù)組的聲明形如 a[d] ,其中a是數(shù)組名字,d是數(shù)組維度,維度必須是一個(gè)常量表達(dá)式。不允許使用 auto 關(guān)鍵字,必須指定數(shù)組的類型。和vector一樣,數(shù)組的元素為對(duì)象,不存在引用的數(shù)組。

初始化數(shù)組時(shí)可以忽略維度,根據(jù)初始值數(shù)量計(jì)算并推測(cè)出維度。

顯式初始化:

const unsigned sz = 3;
int ia1[sz] = {0, 1, 2};
int a2[] = {0, 1, 2};
int a3[5] = {0, 1, 2};  // 等價(jià)于 a3[5] = {0, 1, 2, 0, 0}
string a4[3] = {"hi", "bye"}; // 等價(jià)于 a4[3] = {"hi", "bye", ""}
int a5[2] = {0, 1, 2};  // 這句錯(cuò)誤,初始值過多

對(duì)于字符數(shù)組,還可以用字符串來進(jìn)行初始化:

char a3[] = "c++";  // 等價(jià)于 char a3[] = {'c', '+', '+', '\0'}

??!注意這里有一個(gè) '\0' 結(jié)尾存放空字符,a3的實(shí)際大小為4。

數(shù)組不允許拷貝和賦值 (也有一些編譯器能支持,但是最好不要使用這些非標(biāo)準(zhǔn)特性)

  • 復(fù)雜數(shù)組的聲明

    類型修飾符遵循從右向左結(jié)合的原則。

      int *ptrs[10];             // ptrs是含有10個(gè)整型指針的數(shù)組
      int &refs[10] = ...;       // 錯(cuò)誤,不存在引用的數(shù)組
      int (*Parray)[10] = &arr;  // Parray指向一個(gè)含有10個(gè)整數(shù)的數(shù)組
      int (&arrRef)[10] = arr;   // arrRef引用一個(gè)含有10個(gè)整數(shù)的數(shù)組
    

    對(duì)于 ptrs,首先[10],它是一個(gè)大小為10的數(shù)組,其次 * 表示它存放的是指針,int 是存放的指針指向的類型;

    對(duì)于 Parray,最好由內(nèi)向外地去理解它的含義。圓括號(hào)內(nèi)的 *Parray 表示 Parray 是一個(gè)指針,接下來右邊的 [10] 可以知道 Parray 這個(gè)指針指向大小為 10 的數(shù)組,最左邊的 int 表示數(shù)組的數(shù)據(jù)類型。

    對(duì)于 arrRef,同理,它是一個(gè)引用,引用的內(nèi)容是一個(gè)含有10個(gè)整數(shù)的數(shù)組。

    更復(fù)雜的:

      int *(&arry)[10] = ptrs;
    

    arry 是一個(gè)對(duì)數(shù)組的引用,引用的數(shù)組大小為10,存放的int類型的指針。

  • 訪問數(shù)組元素

    下標(biāo)訪問方式,數(shù)組的下標(biāo)是一個(gè) size_t 類型,它是一種機(jī)器相關(guān)的無符號(hào)類型,足夠大以便能表示內(nèi)存中任意對(duì)象的大小。

    其他的下標(biāo)操作類似于 vector

  • 指針和數(shù)組

    使用數(shù)組的時(shí)候編譯器一般都會(huì)把它轉(zhuǎn)換成指針,可以用取地址符來得到指向數(shù)組元素的指針:

      string nums[] = {"one", "two", "three"};
      string *p = &nums[0];  // p 指向 nums 的第一個(gè)元素
      string *p = nums;      // 等價(jià)于上面一句
    

    在大多數(shù)表達(dá)式中,使用數(shù)組類型的對(duì)象實(shí)際是使用一個(gè)指向該數(shù)組首元素的指針。

    一般數(shù)組名 nums 都會(huì)被轉(zhuǎn)化成 &nums[0],除了使用 decltype(nums) 的時(shí)候,會(huì)返回一個(gè) string[3] 類型。

    對(duì)于空指針,允許給它加上或減去一個(gè)值為 0 的整型常量表達(dá)式,兩個(gè)空指針也允許彼此相減,當(dāng)然結(jié)果為 0

  • 標(biāo)準(zhǔn)庫函數(shù) begin 和 end

    數(shù)組不是類型,因此 begin 和 end 不能作為成員函數(shù)。使用方法:

      int ia[] = {0,1,2,3,4,5,6,7,8,9};
      int *beg = begin(ia);
      int *last = end(ia);
    

    begin 函數(shù)返回指向 ia 首元素的指針, end 函數(shù)返回指向 ia 尾元素的下一位置的指針

!! 標(biāo)準(zhǔn)庫類型的下標(biāo)運(yùn)算符使用的索引值必須是無符號(hào)類型,而內(nèi)置的下標(biāo)運(yùn)算符不同,可以是負(fù)值。例如:

int ia[] = {0, 2, 4, 5, 8};
int *p = &ia[2];     // p指向索引為2的元素
int i = *(p + 2);
int k = p[-2];       // k為ia[0],索引值可以為負(fù)

多維數(shù)組

多維數(shù)組 = 數(shù)組的數(shù)組..

定義一個(gè)數(shù)組元素任然為數(shù)組的數(shù)組時(shí),用兩個(gè)維度來定義它,一個(gè)表示數(shù)組本身大小,一個(gè)表示其元素的大小。

int ia[3][4];  // 大小為3的數(shù)組,每個(gè)元素含有4個(gè)整數(shù)
int arr[10][20][30] = {0};

對(duì)于二維數(shù)組,第一個(gè)維度成為行,第二個(gè)維度稱為列。

int ia[3][4] = {
    {0, 1, 2, 3},
    {4, 5, 6, 7},
    {8, 9, 10, 11}       // 注意內(nèi)部是逗號(hào),括號(hào)都是花括號(hào)
};

也可以寫成一行的形式:

int ia[3][4] = {0, 1, 2, 3, 4, 5, 6,..., 11};

只初始化每一行的第一個(gè)元素:

int ia[3][4] = {{0}, {4}, {8}};

初始化第一行:

int ix[3][4] = {0, 3, 6, 9}; // 加不加花括號(hào)差別很大,對(duì)比上面一句
  • 下標(biāo)引用

    如果給出的下標(biāo)和維度一樣多,那么就是這一點(diǎn)的元素值。如果缺少,就是給定索引處的一個(gè)內(nèi)層數(shù)組。

      int (&row)[4] = ia[1];   // row 是一個(gè)4個(gè)int的數(shù)組的一個(gè)引用,綁定 ia 的第二個(gè)4元數(shù)組上,也就是第二行。
    

    !! 對(duì)于 ia[1],因?yàn)?ia 是一個(gè) int [3][4] 的類型,從左往右看,ia[1]應(yīng)該是一個(gè) int [4] 的類型,是一個(gè)四元數(shù)組。

    在使用范圍 for 語句處理二維數(shù)組時(shí),對(duì)于外層數(shù)組一定要寫引用符號(hào),否則無法通過編譯:

      for(auto row : ia)
          for(auto col : row)  //無法通過編譯
    
      for(const auto &row : ia)
          for(auto col : row)  //可以運(yùn)行
    

    ??! 除了最內(nèi)層循環(huán)外,其他所有循環(huán)的控制變量都應(yīng)該是引用類型

  • 指針和多維數(shù)組

    由多維數(shù)組名轉(zhuǎn)化來的指針實(shí)際上是指向第一個(gè)內(nèi)層數(shù)組的指針。

      int ia[3][4];
      int (*p)[4] = ia;   // p 指向含有4個(gè)整數(shù)的數(shù)組
      p = &ia[2];         // p 指向 ia 的尾元素
    

    使用指針遍歷:

      for(auto p = ia; p != ia + 3; ++p){
          for(auto q = *p; q != *p + 4; ++q)
              cout << *q;
          cout << endl;
      }
      // 也可以更簡潔:
      for(auto p = begin(ia); p != end(ia); ++p){
          for(auto q = begin(*p); q != end(*p); ++q)
              cout << *q;
          cout << endl;
      }
      // 這里 auto 等價(jià)于 int[4] * 
    




4.表達(dá)式

重載運(yùn)算符可以改變運(yùn)算對(duì)象的類型和返回值的類型,但是運(yùn)算對(duì)象的個(gè)數(shù)、運(yùn)算符的優(yōu)先級(jí)和結(jié)合律無法改變。

  • 左值和右值

    rvalue 和 lvalue 左值可以位于賦值語句的左側(cè),而右值不能。

    當(dāng)一個(gè)對(duì)象被用作右值的時(shí)候,用的是對(duì)象的值(內(nèi)容);當(dāng)對(duì)象被用作左值的時(shí)候,用的是對(duì)象的身份(在內(nèi)存中的位置)

    在需要用右值的地方可以用左值代替,但是不能把右值當(dāng)成左值使用。

    • 賦值運(yùn)算符左側(cè)需要一個(gè)(非const)左值作為運(yùn)算對(duì)象,得到的結(jié)果仍然是一個(gè)左值。
    • 取地址符作用于一個(gè)左值對(duì)象,返回一個(gè)指向該運(yùn)算對(duì)象的指針,這個(gè)指針是個(gè)右值。
  • 求值順序

    4種明確規(guī)定了運(yùn)算對(duì)象求值順序的運(yùn)算符:

    • 邏輯與(&&) 先求左側(cè)運(yùn)算對(duì)象的值,只有左側(cè)運(yùn)算對(duì)象的值為真時(shí)才繼續(xù)求右側(cè)運(yùn)算對(duì)象的值
    • 邏輯或(||) 先求左側(cè),左側(cè)為假時(shí)才求右側(cè)表達(dá)式
    • 條件運(yùn)算符(?:)
    • 逗號(hào)運(yùn)算符(,)

算術(shù)運(yùn)算符

一元正負(fù)號(hào)具有最高優(yōu)先級(jí),其次* / %,最低的是+ -,.

c++11中,除了 -m 導(dǎo)致溢出的情況,其他時(shí)候都有 (-m)/n 和 m/(-n) 都等于 -(m/n),m%(-n) 等于 m%n, (-m)%n 等于 -m%n

也就是: (-21)%(-8) = (-21)%8 = -21%8 = -5

邏輯運(yùn)算符

邏輯與和邏輯或都遵循先計(jì)算左側(cè)表達(dá)式的原則(短路求值)。他們的左側(cè)運(yùn)算對(duì)象一般用來保證右側(cè)求值過程中的正確性和安全性,例如:

index != s.size() && !isspace(s[index])

s.empty() || s[s.size() - 1] == '.'  // 遇到空字符串或以句號(hào)結(jié)束的字符串

!!在進(jìn)行比較運(yùn)算時(shí)除非比較的對(duì)象是布爾類型,否則就不要用布爾類型的字面值 true 和 false 作為運(yùn)算對(duì)象。

賦值運(yùn)算符

賦值運(yùn)算符的優(yōu)先級(jí)比較低,在表達(dá)式中盡量加上括號(hào)。

允許使用花括號(hào)括起來的初始值列表作為賦值語句的右側(cè)運(yùn)算對(duì)象:

int k = {3.14};   // 錯(cuò)誤,進(jìn)行了窄化轉(zhuǎn)換
vector<int> vi = {0,1,2,3,4}; // 正確, vi 包含 5 個(gè)元素

賦值語句滿足右結(jié)合律,賦值語句返回的是左側(cè)運(yùn)算對(duì)象:

int ival, jval;
ival = jval = 0;  // 都被初始化為0

其他復(fù)合賦值運(yùn)算符:(都等價(jià)于a = a op b)

+=  -=  *=  /=  %=
<<= >>= &=  ^=  |=

遞增和遞減運(yùn)算符

++和--有前置和后置之分。

int j, i = 0;
j = i++;   // 后置版本得到遞增之前的值, j=0, i=1
j = ++i;   // 前置版本得到遞增之后的值, j=2, i=2

前置版本將對(duì)象本身作為左值返回,后置版本則將對(duì)象原始值的副本作為右值返回。

!! 除非必須,否則不用后置版本的遞增遞減運(yùn)算符。因?yàn)楹笾冒姹就瑫r(shí)存儲(chǔ)了修改前后的兩個(gè)值,如果不需要用修改前的值的話,后置的遞增、遞減就造成一種浪費(fèi)。

后置的++也可以用來輸出前一個(gè)元素,例如循環(huán)輸出一個(gè)vector對(duì)象內(nèi)容直至遇到(但不包括)第一個(gè)負(fù)值為止:

auto pbeg = v.begin();
while(pbeg != v.end() && *beg >= 0)
    cout << *pbeg++;   // 后置++,每次解引用輸出的都是迭代器的前一個(gè)

++ 的優(yōu)先級(jí)比解引用 * 高,因此 *pbeg++ 等價(jià)于 *(pbeg++)

后置的 ++ 操作返回值是未自增前的那個(gè) pbeg,因此 pbeg 自增后,解引用的仍然是原來沒自增的那個(gè)迭代器。

!!注意 *pbeg++ 這種寫法,常用。

成員訪問運(yùn)算符

ptr->mem 等價(jià)于 (*ptr).mem

解引用運(yùn)算符的優(yōu)先級(jí)低于點(diǎn)運(yùn)算符!所以上式必須加括號(hào)。

箭頭運(yùn)算符作用于一個(gè)指針類型的運(yùn)算對(duì)象,結(jié)果是一個(gè)左值。點(diǎn)運(yùn)算符的結(jié)果則根據(jù)成員所屬的對(duì)象是左值和右值而定。

條件運(yùn)算符

簡單的 if-else 語句可以用條件運(yùn)算符(?:)實(shí)現(xiàn)

條件運(yùn)算符可以嵌套:

finalgrade = (grade > 90) ? "high pass" 
                          : (grade < 60) ? "fail" : "pass";

最好不要嵌套太多層。

!!在輸出表達(dá)式中,由于條件運(yùn)算符的優(yōu)先級(jí)非常低,因此兩邊一般要加上括號(hào)。

cout<< ((grade < 60) ? "fail" : "pass") <<endl;

位運(yùn)算符

運(yùn)算符 功能
~ 位求反
<< 左移
>> 右移
& 位與
^ 位異或
| 位或

??!對(duì)符號(hào)位的處理沒有規(guī)定,因此強(qiáng)烈建議僅對(duì)無符號(hào)類型進(jìn)行位運(yùn)算

sizeof運(yùn)算符

兩種形式:

sizeof(type)
sizeof expr

sizeof 滿足右結(jié)合律,且優(yōu)先級(jí)和 * 一樣。有 sizeof *p 也就是 sizeof(*p)。

因?yàn)閟izeof實(shí)際不會(huì)去求表達(dá)式的值,所以即使 p 是一個(gè)無效(未初始化)指針也無所謂。

  • 對(duì) char 類型或者類型為 char 的表達(dá)式執(zhí)行 sizeof 運(yùn)算,結(jié)果得 1
  • 對(duì)引用類型執(zhí)行 sizeof 運(yùn)算,得到被引用對(duì)象所占空間的大小
  • 對(duì)指針執(zhí)行 sizeof 運(yùn)算,得到指針本身所占空間大小
  • 對(duì)解引用指針執(zhí)行 sizeof 運(yùn)算,得到指針指向的對(duì)象所占空間的大小,指針不需要有效
  • 對(duì)數(shù)組執(zhí)行 sizeof,得到整個(gè)數(shù)組所占空間的大小,sizeof不會(huì)把數(shù)組轉(zhuǎn)化為指針處理
  • 對(duì) string 對(duì)象或 vector 對(duì)象運(yùn)算只返回該類型固定部分的大小,不會(huì)計(jì)算對(duì)象中的元素占用了多少空間

類型轉(zhuǎn)換

如果兩種類型可以相互轉(zhuǎn)換,那么他們就是關(guān)聯(lián)的。

  • 隱式轉(zhuǎn)換:

    • 在大多數(shù)表達(dá)式中,比 int 類型小的整型值首先提升為較大的整數(shù)類型
    • 在條件中,非布爾值轉(zhuǎn)換成布爾值
    • 初始化過程中,初始值轉(zhuǎn)化成變量的類型;賦值語句中,右側(cè)的對(duì)象類型轉(zhuǎn)換成左側(cè)的
    • 算術(shù)運(yùn)算和關(guān)系運(yùn)算中,多種類型都轉(zhuǎn)換成同一種類型
    • 函數(shù)調(diào)用也會(huì)發(fā)生類型轉(zhuǎn)換
    • 數(shù)組轉(zhuǎn)換成指針,但是在 &、sizeof、decltype下不會(huì)轉(zhuǎn)化
  • 整型提升:

    負(fù)責(zé)把小整數(shù)類型轉(zhuǎn)換成較大的整數(shù)類型。對(duì)于bool、char、signed char、unsigned char、short和unsigned short,只要他們所有可能的值都能存在 int 里,他們就會(huì)被提升成 int 類型。

    較大的 char 類型(wchar_t、char16_t、char32_t)提升成int、unsigned int、long、unsigned long、long long和unsigned long long 中最小的一種類型。

    在計(jì)算時(shí),首先進(jìn)行整型提升,如果類型匹配則進(jìn)行計(jì)算,如果提升后的兩個(gè)運(yùn)算對(duì)象一個(gè)帶符號(hào)一個(gè)無符號(hào),且無符號(hào)類型不小于帶符號(hào)的,則帶符號(hào)的會(huì)被轉(zhuǎn)化成無符號(hào)的,例如unsigned int 和 int 會(huì)使 int 轉(zhuǎn)化成無符號(hào)的。

  • 顯示轉(zhuǎn)換:

    一個(gè)命名的強(qiáng)制類型轉(zhuǎn)換具有如下形式:

      cast-name<type>(expression);
    

    cast-name有四種:

    • static_cast: 任何不包含底層const的都可以使用

        double slope = static_cast<double>(j) / i;
      
    • const_cast: 只能改變運(yùn)算對(duì)象的底層const,“去掉const性質(zhì)”,一般用于有函數(shù)重載的上下文中

        const char *pc;
        char *p = const_cast<char*>(pc); // 正確,但是不能通過 p 寫值
      
    • dynamic_cast

    • reinterpret_cast:通常為運(yùn)算對(duì)象的位模式提供較低層次上的重新解釋

    !!盡量不要使用強(qiáng)制類型轉(zhuǎn)換





5.語句

  • 空語句:只包含一個(gè)單獨(dú)的分號(hào),最好加上注釋

    也可以寫一個(gè)空塊

      while(cin >> s && s != sought)
      { }  // 空塊
    
      while(cin >> s && s != sought)
          ; // 空語句
    
  • 懸垂 else :

    當(dāng)一個(gè)if語句嵌套在另一個(gè)if語句中時(shí),可能if分支會(huì)多于else分支。c++規(guī)定else與離它最近的尚未匹配的if匹配,這點(diǎn)和python不同!

    最好用花括號(hào)控制執(zhí)行路徑:

      if(grade % 10 >= 3) {   // 花括號(hào)必不可少!
          if(grade % 10 > 7)
              lettergrade += '+';
      } else
          lettergrade += '-';
    
  • switch 語句:

      switch (ch) {
          case 'a':
              ++aCnt;
              break;
          case 'b':
              ++bCnt;
              break;
          ...
          default:
              ++otherCnt;
              break;
      }
    

    如果某個(gè) case 標(biāo)簽匹配成功,將從該標(biāo)簽往后順序執(zhí)行所有 case 分支,如果不 break,直到 switch 結(jié)束時(shí)才會(huì)停下來。

    或者把他們都寫在一行里也是一種合法的語句。

      switch (ch) {  // 只要是5個(gè)字母中的任意一個(gè)都會(huì)執(zhí)行后面的++
          case 'a':
          case 'e':
          case 'i':
          case 'o':
          case 'u':
              ++vowelCnt;
              break;
      }
    
      switch (ch) {
          case 'a': case 'e': case 'i': case 'o': case 'u':
              ++vowelCnt;
              break;
      }  // 一般不要省略分支后面的break語句,如果要省略,最好加上注釋!
    

    如果需要為某個(gè) case 分支定義并初始化一個(gè)變量,應(yīng)該把變量定義在塊內(nèi),加上一個(gè) { }

  • 跳轉(zhuǎn)語句

    break 語句負(fù)責(zé)終止離他最近的 while、 do while、 for 或 switch 語句。僅限于最近的一個(gè)循環(huán)或者 switch,只跳出一層循環(huán)。

    continue 語句終止最近的循環(huán)的當(dāng)前迭代,并立即開始下一次循環(huán)。

  • try 語句塊

    異常處理包括: throw 表達(dá)式,引發(fā)異常; try 語句塊,處理異常,以關(guān)鍵字 try 開始,以一個(gè)或多個(gè) catch 子句結(jié)束,稱為異常處理代碼;一套異常類,用于在 throw 表達(dá)式和相關(guān)的 catch 子句之間傳遞異常的具體信息。

    拋出異常:

      if(item1.isbn() != item2.isbn())
          throw runtime_error("Data must refer to same ISBN");
      cout << item1 + item2 <<endl;
    

    異常處理代碼:

      try {
          ...
          if(item1.isbn() != item2.isbn())
              throw runtime_error("Data must refer to same ISBN");
          ...
      } catch(runtime_error err) {
          cout << err.what() << "\nTry again? Enter y or n: " <<endl;
          char c;
          cin >> c;
          if(!cin || c == 'n')
              break;
      }
    

    一個(gè) try 語句塊拋出多個(gè)異常,后面接多個(gè) catch 子句進(jìn)行處理

    標(biāo)準(zhǔn)異常:<stdexcept>定義的異常類

    exception 最常見的問題
    runtime_error 只有在運(yùn)行時(shí)才能檢測(cè)出的問題
    range_error 運(yùn)行時(shí)錯(cuò)誤:結(jié)果超出了有意義的值域范圍
    overflow_error 運(yùn)行時(shí)錯(cuò)誤:計(jì)算上溢
    underflow_error 運(yùn)行時(shí)錯(cuò)誤:計(jì)算下溢
    logic_error 程序邏輯錯(cuò)誤
    domain_error 邏輯錯(cuò)誤:參數(shù)對(duì)應(yīng)的結(jié)果值不存在
    invaild_argument 邏輯錯(cuò)誤:無效參數(shù)
    length_error 邏輯錯(cuò)誤:試圖創(chuàng)建一個(gè)超出該類型最大長度的對(duì)象
    out_of_range 邏輯錯(cuò)誤:使用一個(gè)超出有效范圍的值
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容