把《c++ primer》讀薄(1-2前言+變量和基本類型)


督促讀書,總結精華,提煉筆記,拋磚引玉,有不合適的地方,歡迎留言指正。

一:大小端的概念

Big-Endian和Little-Endian(見計算機存儲的大小端模式解析

二:浮點數的機器級表示

(見從如何判斷浮點數是否等於0說起——浮點數的機器級表示

三:c++的基本的內置類型:

1)算術類型,又包括:

整型(包括:整數int、short、long int類型,單個字符(分為存儲單個機器字節的char類型,1個字節,char有三種不同的類型,普通char,unsigned char和signed char,一共兩個表達方式unsigned char和signed char。和存儲漢字和日語等的擴展字符集的wchar_t類型,2個字節),布爾值0,1)。

整型又分為無符號和有符號的(除bool類型之外)

在選用整數類型的時候,一定先分析好大概范圍,一些機器int和long int一樣,如果貿然總使用范圍很大的類型,則額外代價較高,總用范圍嬌小的,又容易溢出。

浮點型,float,double,long double。一般用double就足夠了,且很多經典書籍都建議使用double,少用long doouble,因為一沒必要那么高精度,二運行起來代價額外很大。

2)還有一種void ,空類型。

問題1:在輸出漢字字符串的時候會亂碼,一般解決方案是字符串前面加L,why?

這是在說明字符串的字面值是寬字符wchar_t,  L"大家好!"   這樣表示就可以了。因為編譯器有時候默認使用unicode字符集,不加L顯示的日文或者韓文,L代表寬字符

問題2:針對模的問題

     unsigned char chr = 255;
     printf("%u\n", chr);//255
 
     unsigned char chr1 = 256;
     printf("%u\n", chr1);//1
     //在c++或者c,這樣的賦值是能被接受的
     //超出范圍的數值%取值范圍,unsigned char是0-255,就是336模256之后的值再賦值chr2
     unsigned char chr2 = 336;
     printf("%u\n", chr2);//80
     //-1顯然已經超出了范圍,但是-1%256應該還是-1的,這里打印255,是因為-1是負數,而我們打印的是無符號數
     //編譯器就認為-1的補碼1111 1111,是無符號數的補碼,打印出來那就是255
     unsigned char chr3 = -1;
     printf("%u\n", chr3);//255
     //同理,-1模無符號int取值范圍之后還是-1,且-1的32位補碼16進制是0xffff ffff,編譯器認為是無符號的數的補碼,那就是32位無符號int類型的最大值4294967295
     unsigned int in = -1;
     printf("%u\n", in);//4294967295
     //相對的,使用%d打印就是-1,因為編譯器認為這是帶符號數的補碼
     printf("%d\n", in);//-1
 
     int in1 = -1;
     printf("%d\n", in1);//-1

     unsigned int in = 4294967296;

     printf("%u\n", in);//0

4294967296模4294967296就是0

//%運算符只能用於整數相除求余,運算結果的符號與被除數相同,絕對不能用於實數。
//被除數=商 x 除數 + 余數
//余數的絕對值一定小於除數的絕對值

 printf("%d", 5 % 3);//2
    printf("\n%d", 3 % 5);//3
    printf("\n%d", 3 % -5);//3,3=0 * -5 + 3
    printf("\n%d", -3 % 5);//-3,-3=0 * 5 + -3
    //一定要思路清晰,腦子里有數學的概念就行
    printf("\n%d", 5 % -3);//2,5=-1 * -3 + 2

問題3:沒有short類型的字面值常量

字面值常量比如:

有無符號int類型111u,有默認帶符號的int類型111,有long int類型111L,有無符號long int類型111LU或者111UL,有默認的double類型,1.1,有單精度類型1.1F,有擴展精度類型1.1L,有true,false的bool字面值,有字符和字符串類型的字面值。就是沒有short int類型字面值!

問題4:多行字面值的表示

cout << "aaaaaaaaa" "ddddddd" << endl;//ok
    cout << "aaaaaaaaaaaa"
        "bbbbbbbbbbbbbb" << endl;//ok
    cout << "aaaaaaaa\
            bbbbbbbbbbb" << endl;//ok
    //cout << "aaaaaaaaaaaa
    //    bbbbbbbbbb" << endl;//error

針對第三行代碼,\  雖然可以實現多行字面值字符串的輸出輸入,但是有局限,\  必須在本行末尾,后面不能有注釋和其他字符,且后續行bbbb前面的空,都算作了字符串字面值的一部分,故出現如圖第三行所示的情況,解決辦法:

    cout << "aaaaaaaa\
bbbbbbbbbbb" << endl;

問題5:程序盡量的不要過多的依賴機器

比如,int在win32下是一個長度,也許換個機器就不一樣了,如果有,最好是在依賴處都有說明,否則程序移植起來很困難。

問題6:c和c++的變量為什么要先聲明還要定義名字,使用前為什么還必須先定義變量或者常量的類型?

c/c++是靜態類型的語言(編譯時做類型檢測),對程序的操作是否合法都是在編譯期間檢測,故編譯器必須能夠識別程序內每個實體,對於不是關鍵字的且沒有唄引號括起來的符號,對於c/c++編譯器來說,它是不認識的,如何讓編譯器知道這個單詞是什么意思呢?

所以就有了聲明(declare)。編譯器每次開始工作,不知道有哪些變量,不知道有哪些函數,也不知道有哪些符號常量,它不是人!如從代碼里讀了一個單詞,既不是關鍵字,又不是自己認識的東西,編譯器就認為這是一個沒有聲明的東西,因為不認識它,所以不知道如何處理。所以“聲明”,就是告訴編譯器有這么一個東西。聲明的時候主要做四件事: 

一是建立變量符號表

聲明變量,編譯器可以建立變量符號表,如此,程序用到多少變量,每個變量類型是什么,編譯器非常清楚,是否使用了沒有聲明的變量,編譯器在編譯期間就可以發現,從而幫助了程序員遠離由於疏忽而將變量名寫錯的情況。 

二是變量的數據類型指示系統分配多少內存空間

數據類型指示系統如何解釋存儲空間中的值。同樣的數值,不同的類型將有不同的解釋。32位機器,一般情況下,int占4個字節,float占4個字節,在內存中同樣存儲二進制數,且這個二進制數也沒有標志區分當前是int還是float型,那么如何區分?就是通過變量的數據類型來區分。由於聲明建立了變量符號表,所以系統知道變量存儲空間該如何解釋。

三是變量的數據類型確定了該變量的取值范圍

例如短整型short的數據取值-32768~32767之間。

四是不同的數據類型有不同的操作

比如,模運算,不能用於浮點數。如下:

printf("%d", 5 % 2);// 1
printf("%d", 5.0 % 2);// error
printf("%d", 10.0);// %d對應解析帶符號10進制整數,實數解析會失敗,結果為0。這種陷阱題,基礎題,經驗題往往確定一個人的水平
printf("%d", (int)10.0);//使用強制類型轉換,ok!
printf("%f", 1);//因為%f對應解析實數,如果是整數,轉化失敗就是0.000000,同樣可以強制類型轉換改正。

問題7:理解初始化和賦值是兩個完全不同的概念

c++支持兩種初始化方式,復制初始化(使用=),和直接初始化(常用於構造函數的初始化列表),且使用直接初始化,語法更靈活,效率更高(對內置類型沒什么區別),專家推薦多用!

    double d = 1.0;//ok,復制初始化
    double dd(1.11);//ok,直接初始化,想起了構造函數的初始化列表

初始化:創建新的變量,並給變量賦一個初始值的過程。

賦值:變量早在前面聲明過,且有了初始值,此時再給它一個新值(把前面的值擦掉)的過程。

    string boy("dadadadadafwe"), girl = "   ";//ok,混搭沒問題!
    int a , b = 10, c, d = 100;//ok
    char chr = 'a', chr1('0');//ok

注意

    //int a = a;//error,提示變量a沒有唄初始化就使用!但是一些編譯器就不會報錯,建議不要這樣寫。
    //string str1 = str2("dadafa");//error,要分開,分別初始化
    //double a = b = c = 1.0;//error,同樣要分別分開初始化!
    string str1 ="da", str2(" ");
    double a = 0.0, b = 0.1, c = 0.2;

問題8:關於自動初始化

在函數外定義的變量自動初始化為0,函數內就不會自動初始化,且對於c和c++的基本內置類型,最好定義的同時就初始化,即使當下沒用。

#include <iostream>
#include <string>
using namespace std;

string str1;//空字符串
int a;//0
double d;//0
float f;//0
char c;//空字符

int main(void)
{
    string str;//ok,string對象創建,自動調用string類的默認構造函數,進行初始化
    cout << str << endl;//str自動初始化為空字符串
    cout << a << endl;//0
    cout << str1 << endl;//
    cout << d << endl;//0
    cout << f << endl;//0
    cout << c << endl;//

    return 0;
}

針對c++,還需要考慮類的構造函數

問題9:變量定義和聲明的區別

因為c和c++經常是多文件開發,故有了聲明和定義

變量的定義是為了給變量分配內存,指定初始值,一個變量只能有一個定義存在。

聲明變量,是告訴程序變量的類型和名稱

即定義也是聲明,因為定義變量的同時,也聲明了變量的名字和類型,一個變量可以有多次聲明,但是定義只有一次!因為聲明僅僅指定名稱和類型,不分配內存

通常用關鍵字extern專門進行變量的聲明,而不定義。告訴本文件,這個變量已經定義在了其他文件。俗稱外鏈接。

extern double d;//僅僅是變量的聲明
extern double d;//同一個變量,可以被聲明多次!
double d = 0.0;//變量的聲明,同時包含了定義
//double d;變量僅僅只能被定義一次

int main(void)
{
    return 0;
}

同樣,聲明也可以變為定義,如果聲明有初始化,那么就變為定義,針對extern語句僅僅在函數外部(擁有文件作用域范圍),才可以變為定義,否則出錯!

#include <iostream>
#include <string>
using namespace std;

extern int a = 1;//聲明變為了定義
//int a;“int a”: 重定義

int main(void)
{
    //extern int a = 1;//在函數內部,這樣不對
//“a”: 不能對帶有塊范圍的外部變量進行初始化,想實現聲明變定義,那么必須給extern的變量 文件作用域范圍,也就是寫在函數外部,使得a成為一個全局變量
    return 0;
}

回憶,多文件開發,都是在一個文件進行變量的定義,而其他文件使用這些變量就僅僅需要包含這些變量的聲明 即可,無需(也不可以)重定義。

再看:

    int sum = 0;

    for (int i = 0; i != 10; i++)
    {
        sum += i;
    }

    cout << i << endl;//error,因為i的作用域在for循環體內有效,for外部沒有定義變量i,故報錯!
    //“i”: 未聲明的標識符

問題10;避免硬編碼

    int sum = 0;

    /*for (int i = 0; i < 512; i++)
    {
        sum += i;
    }判斷語句的512就是硬編碼,如果程序很大,那么可讀性就很差(無法從上下文獲得512的含義),且如果類似的for語句很多,一旦需求變化,那么想改就不那么容易了!
    應該多多用const對象*/
    const int j  = 512;
    for (int i = 0; i < j; i++)
    {
        //
    }

這樣做,如果常量定義出錯,或者被無心的修改,那么可以被編譯器立馬檢測出來!程序健壯性提高!因為常量定義后不能被修改,故常量定義的同時必須馬上初始化!

問題11:引用初步探討

int a = 1024;
double d = 0.1, dd = 0.2, ddd = 2.2;
int &ref = a;//ok
double &refd(d);//ok
double &refd1 = dd, &refd2 = ddd;//ok

多個方法定義多個引用沒問題,記住:引用就是外號而已!不占新的內存空間,和原數值共享內存空間。

注意,引用必須初始化!因為引用必需要綁定到一個實體對象!故不能是引用的引用。也不能不初始化!

int &ref1;    //error C2530:“ref1”: 必須初始化引用
int i = 1024;
int &refint = i;//ok
int &refint1 = i;//ok,可以給一個對象,起多個外號
int &refint2 = refint;//ok
int &&refint3 = i;//error C2440: “初始化”: 無法從“int”轉換為“int &&” 無法將左值綁定到右值引用

引用一旦綁定一個對象,不能再綁定其他對象

double d = 1.0;
double dd = 1.1;
double &refd = d;
double &refd = dd;// error C2374: “refd”: 重定義;多次初始化

引用要注意字面值常量的綁定問題

int &ref2 = 1024; // error C2440: “初始化”: 無法從“int”轉換為“int &”
//因為1024是字面值常量,而ref2是普通引用,ref2是一個變量,又常量不能被修改,故用變量綁定肯定報錯!
//修改
const int &ref2 = 1024;//ok

同理,const常量必須用const引用來綁定!

//const常量必須用const引用來綁定!
const char c = 'a';
//char &refc = c;  error C2440: “初始化”: 無法從“const char”轉換為“char &” 轉換丟失限定符
const char &refc = c;//ok

注意引用和不同類型變量的綁定問題

//把引用綁定到不同類型的對象
int i = 512;
//double &refi = i;  //error C2440: “初始化”: 無法從“int”轉換為“double &”
//使用const引用就對了
const double &refi = i;

原因:對於不同類型的引用綁定過程,編譯器內部做了這樣的優化,先針對int類型的i = 512,自動生成一個double類型的臨時變量

double temp = 512;

再讓double引用去綁定temp,而不是我們看到的直接綁定int i=512;

double &refi = temp;

故這里有一個問題,現在refi是非const類型,那么說明可以對refi也就是i修改,但是事實上,我們修改只是修改了temp,不會對原值i修改,這違背了引用的初衷,故這樣是定義為不合法的行為。同理,使用const引用就ok了,因為僅僅是使用i的值,並不修改,這樣是完全合法的。

問題12:盡量多用枚舉定義常量

//c++的枚舉類型可以省略美劇名字,也可以加上名字,枚舉本身就是常量表達式!不能修改它的成員!

    enum {}; //勿忘;
    //枚舉默認第一個變量賦值為0,以后的依次+1,如果有初始值,則按照初始值,以后依次+1
    enum e{
        a, b, c
    };
    //a =0,b =1, c=2

//使用枚舉定義de 常量,是真正的常量!不受限於類的對象!且有很好的可讀性!含義的聚集性!

比如在類中定義const成員,我們不能在類里初始化,必須創建對象的時候,構造函數初始化。看似是常量,但是針對的只是某一個對象,其他對象的話,仍然可以去初始化屬於自己的那一份const成員。並不是真正的constant!而枚舉就無所謂這個問題。

    enum ee{
        aa = 1, bb, cc
    };
    //bb = 2, cc =3
    enum eee{
        aaa = 1, bbb, ccc = 4, ddd
    };
    //bbb = 2, ddd = 5

//枚舉是一種類型!可以定義枚舉類型的對象,同理也可以給枚舉類型的對象賦值。賦值,必須是這個枚舉類型的成員,或者是同一枚舉類型的其他的枚舉對象!

    eee e1;//ok,定義枚舉類型的對象e1
    //eee e2 = 1;//error,賦值過程錯誤,應該是本枚舉類型的成員
    //eee e0 = aa;//error, 賦值錯誤,應該是同類型的枚舉對象,aa是屬於ee這個枚舉的
    //修改
    eee e2 = aaa;
    eee e3 = e2;

問題13:c++也支持結構類型

區別就是c++的結構里,可以有函數,且默認的成員訪問屬性是public,而class是private。

問題14:回答問題,類A里的private成員int a;都是誰能直接去訪問?

千萬不能忘記友元!當然還有類A內部的組成部分也可以直接訪問。

問題15:規范化的頭文件

頭文件不應該含有變量或者函數的定義!只能有聲明!但是頭文件也有例外,那就是頭文件可以定義類,頭文件可以定義編譯時就知道具體值的const對象,可以定義inline函數。

問題16:什么是常量表達式?

編譯器在編譯的時候,就能夠計算得出表達式的明確結果的表達式表達式由常量表示:

double a = sizeof(double);

兩個關鍵點:一是必須是編譯的時候就能計算出明確結果的表達式,二是表達式組成部分都必須是常量,不能包含變量,比如1+2是常量表達式,整型字面值常量就是常量表達式,枚舉常量也是常量表達式!如果定義a為常量1,那么a+2也是常量表達式。如果定義變量a,那么a+2就不是常量表達式。

問題17:解答前面const對象默認在函數外部定義,為什么是局部作用域?(需顯式的加extern才是全局的,不同於一般變量)

15說了,頭文件只能有聲明,但是const常量的定義卻可以,因為c++里,const常量在函數外部定義的話,c++規定為局部變量(本文件可見,而本文件外的本程序其他文件不可見),故不會造成重定義現象。

注意更深層次的原因:

如果const對象使用常量表達式初始化,那么該變量(變為常量的變量,僅僅說的是這個變量,不是常量表達式本身!)不會被分配內存空間,因為絕大部分的編譯器編譯時,都直接使用的相應的常量表達式來替換掉該const變量,這需要理解!所以,如果const對象在頭文件內的函數外部沒有被常量表達式初始化,那么就不應該定義在頭文件,應該和其他變量一樣定義在源文件並初始化。然后在頭文件添加extern聲明即可被共享。比如:

const double d = squt(2.11);//一個函數計算的返回值初始化d,此時編譯的時候無法確定squt(2.11)的返回值!必須等到執行期間
//所以,d不是被常量表達式初始化的,那么這個語句應該放到源文件!而不是頭文件

這里具體意思應該是說,頭文件的const,一方面,定義在了函數外部,是局部作用域,局部的那就是互不影響!另一方面,就是大部分const變量都是用常量表達式初始化!上面說了,編譯器不會為這樣的const變量分配內存,而是使用對應的常量表達式替換該變量名!等價於,聲明!那么這里有一個名詞叫:

常量折疊

至今沒有找到一些權威的論證,網上的各個論壇博客,都說的有一定的道理,但是具體細節又各有不同的爭論,還沒有什么權威的說法,具體涉及到了編譯原理的知識。

再說說c語言里的const常量

《c primer plus》一書清晰的說到:c語音里的const定義的常量,不被看為常量,而是只讀變量!《c和指針》一書也說到,c中const定義的常量,僅僅能用在應該使用變量的地方,比如設置數組維數,使用const定義的常量就報錯!當然這是c99之前。如今c99也支持了這一做法!如下:

        const int const_m = 10;//在c語言(不同於c++)里,const值不被看作是常量
    int n = 100;//定義了變量
    double d1[10];// ok
    double d2[5 * 2 + 1];//ok
    double d3[];//error,沒有初始化,也沒有大小指定
    double d4[sizeof(int)];//ok,sizeof表達式在c里被認為返回一個整數常量
    double d5[-10];//error C2118: 負下標
    double d6[0];//error C2466: 不能分配常量大小為 0 的數組
    double d7[3.14];// error C2058: 常量表達式不是整型
    double d8[(int)3.14];//ok

    double d9[const_m];// error C2057: 應輸入常量表達式,“d9”未知的大小
    double d10[n];//error C2057: 應輸入常量表達式,“d9”未知的大小
c99之后,后兩種方式可以了,並且創建了一種新數組VLA(variable length array)變長數組。目的是為了讓c更適合做數值計算。
總之,記住,在c里要看編譯器支持的標准。在c++里,當const變量是常量表達式初始化的,那么寫在頭文件,如果不是,最好寫到各自需要的源文件,在其他文件,使用extern關鍵字聲明以下,即可共享。
當然不寫也不算錯,只是規范的編程習慣。

問題18:給頭文件設置頭文件保護符,避免重定義,也就是多重包含

#ifndef XXXXX_H//一般是大寫,避免名字沖突
//該預處理指示的作用是檢測   指定的預處理器變量  是否沒定義
//if not define的縮寫,如果沒有定義,那么其后的所有指示都被執行一次,截至到#endif
//如果該預處理器變量已經被定義了,那么就不再執行其后的語句,避免了重復包含和重定義。
#define XXXXX_H//該指示的作用是接受一個名字,並定義  該名字為  預處理器變量
//……
//……
#endif//結束指示

 

歡迎關注

dashuai的博客是終身學習踐行者,大廠程序員,且專注於工作經驗、學習筆記的分享和日常吐槽,包括但不限於互聯網行業,附帶分享一些PDF電子書,資料,幫忙內推,歡迎拍磚!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM