督促讀書,總結精華,提煉筆記,拋磚引玉,有不合適的地方,歡迎留言指正。
一:大小端的概念
Big-Endian和Little-Endian(見計算機存儲的大小端模式解析)
二:浮點數的機器級表示
三: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電子書,資料,幫忙內推,歡迎拍磚!