C語言知識點
C 中的注意點
對于C來講曾雕,最麻煩的是不斷的malloc 和 free斯够。特別是對于三階指針那種情況啸蜜。建議每次malloc之后對著找有沒有對應(yīng)的free髓棋。對于不是很大的內(nèi)存申請实檀,或者復(fù)用程度高的,建議直接棧里面申請按声。
對于free掉之后的空間膳犹,注意之前申請的指向的指針得置NULL。
對于字符串签则,每次malloc的時候記得 加 1须床,為 ‘\0’ 留個位置。
-
C語言對所有的數(shù)組指針都不知道具體有效值有多長渐裂,都需要額外的信息進行記錄和操作豺旬。
入?yún)?shù)里面要注明數(shù)據(jù)長度,返回參數(shù)中要對返回值是一個數(shù)組的情況,注明各個維度的具體數(shù)值鲤遥。可以參考uthash實例二看看具體的參數(shù)控制操作。
輸入: ["eat", "tea", "tan", "ate", "nat", "bat"] 輸出: [ ["ate","eat","tea"], ["nat","tan"], ["bat"] ] char *** groupAnagrams(char ** strs, int strsSize, int* returnSize, int** returnColumnSizes)
入?yún)?shù)的維度都比較好理解钢坦,但是出參的話前面會多加一個
*
使用指針傳遞方便參數(shù)的傳回逛万。如returnSize其實就是個標(biāo)量得封,表明輸出的二維數(shù)組里面有幾個一維數(shù)組,但是前面加了*
。同理可以理解一維數(shù)組 returnColumnSizes 在參數(shù)列表里為什么是兩個*
, 其實就是傳入了指向指針的指針。在函數(shù)里為returnColumnSizes開辟空間的時候需要先解引用慷彤。
*returnColumnSizes = (int *)malloc(sizeof(int) * strsSize);
-
確保有符號整數(shù)運算操作不出現(xiàn)溢出锚沸。
有符號整數(shù)的溢出,算術(shù)運算的結(jié)果太大超過設(shè)定的位寬就會發(fā)生溢出,一般會導(dǎo)致符號位發(fā)生轉(zhuǎn)變。
有符號數(shù) a 和 b 的 加法檢查硅确。
(b > 0) && (a > INT_MAX - b)
或者(b < 0) && (a < INT_MIN - b)
柿估⌒迕剩總結(jié)下來就是如果 b 為 正,防止 a 和 b 相加大于 INT_MAX 零抬。如果 b 為 負(fù)褥芒,防止 兩個負(fù)數(shù)加起來能夠小于 INT_MIN。有符號數(shù) a 和 b 的除法檢查。除了除0檢查,也要防止整數(shù)溢出。同理乘法也要先判斷
numA > (INT_MAX / numB)
成立與否痊土,成立的話就有溢出酌呆。if ((numB == 0) || ((numA == INT_MIN) && (numB == -1))) { 錯誤處理 } result = numA / numB;
-
無符號操作數(shù)的計算倒是不會發(fā)生溢出,不過當(dāng)計算結(jié)果太大超出表示范圍的時候,就會對結(jié)果類型能夠表示的最大值加1再進行求模。舉個例子:
UINT8 test_unsigned = 255; UINT8 result = test_unsigned + 3; printf("%d", result);
輸出結(jié)果就是
(255 + 3) mod 256 = 2
這種情況叫做無符號整數(shù)運算操作出現(xiàn)反轉(zhuǎn)或者回繞拴念。進行數(shù)值加法的時候需要進行是否大于
UINT_MAX
的判斷队魏。 整形轉(zhuǎn)換時候的各類隱藏錯誤。譬如出現(xiàn)截斷錯誤刽虹,整型表達式賦值給較大類型前未轉(zhuǎn)換為較大類型,造成數(shù)據(jù)溢出呢诬。
C99標(biāo)準(zhǔn)中有明確提到整數(shù)提升的概念:"如果int能夠表示原始類型中的所有數(shù)值涌哲,那么這個數(shù)值就被轉(zhuǎn)成int型,否則尚镰,它被轉(zhuǎn)成unsigned int型狗唉。這種規(guī)則被稱為整型提升。所有其它類型都不會被整型提升改變。"
char/unsigned char,short/unsigned short都可以被int表示辈灼。所以當(dāng)它們做算術(shù)運算時唠雕,都會被提升成int類型点弯。-
符號位擴展知識:要擴展量為有符號量,不管最終要擴展成有符號還是無符號,都遵循符號擴展挠蛉;要擴展量為無符號教寂,不管最終要擴展成有符號還是無符號址芯,都遵循零擴展旬陡。
int main() { char a = 0xff; // a為-1,其為有符號量蔬螟,二進制為11111111 unsigned short b = a; //此處a要進行符號擴展,遵循符號擴展,b的二進制為11111111 11111111 short c = a; //此處a要進行符號擴展,遵循符號擴展,c的二進制為11111111 11111111 printf("c = %d\n", a); printf("b = %u\n", b); printf("c = %d", c); return 0; }
int main() { unsigned char a = 0xff; // a為:255沐飘,其為無符號量游桩,二進制為11111111 unsigned short b = a; //此處a要進行符號擴展,遵循零擴展,b的二進制為00000000 11111111 short c = a; //此處a要進行符號擴展,遵循符號擴展,c的二進制為00000000 11111111 printf("c = %d\n", a); printf("b = %u\n", b); printf("c = %d", c); return 0; }
從上面的結(jié)果可以看到符號位的擴展方法只和待擴展變量的符號有關(guān)耐朴,如果待擴展的變量是無符號變量借卧,則遵循零擴展,否則就是符號位擴展筛峭。
有符號整數(shù)使用位運算操作符時候的錯誤铐刘。特別是左移時候把符號位覆蓋的情況。
-
無符號數(shù)和有符號數(shù)互相計算和比較的時候蜒滩,有符號數(shù)會先變?yōu)闊o符號數(shù)再參與計算滨达。所以避免有符號數(shù)和無符號數(shù)之間的邏輯運算,算術(shù)運算的結(jié)果倒是還好俯艰,因為會根據(jù)最后存放結(jié)果值的定義類型不同進行轉(zhuǎn)換捡遍,不會有太大問題。
int a=-1; unsigned int b=2; if(a>b) { printf("-1>2"); } // 輸出 "-1>2" int main() { int a=2147483647; unsigned int b=1; printf("a+b=%d\n",a+b); printf("a+b=%u\n",a+b); printf("%d\n",2147483648); return 0; } 結(jié)果: a+b=-2147483648 a+b=2147483648 -2147483648
字符類型的數(shù)值是由實現(xiàn)定義的竹握,可能為有符號數(shù)画株,也能為無符號數(shù)(unsigned char)。字符可以參與數(shù)值的加減計算啦辐, 例如輸出ASCII碼的b谓传,
printf("%c\n", 'a' + 1);
單目運算符的優(yōu)先級一般大于雙目和三目運算符。
size_t
類型是sizeof操作符結(jié)果的無符號整數(shù)類型芹关,具有size_t
類型的變量能夠保證具有足夠的精度來表示對象的大小续挟。size_t
的最大值由SIZE_MAX
宏表示。
C基礎(chǔ)知識點
存儲區(qū)
BSS段:BSS段(bss segment)存放未初始化的全局變量(包括靜態(tài)全局變量)和初始化為0的全局變量(包括靜態(tài)全局變量)侥衬,屬于靜態(tài)分配內(nèi)存诗祸。BSS段的變量都有默認(rèn)的初始值0, 而在動態(tài)數(shù)據(jù)區(qū)(堆區(qū),棧區(qū))變量的默認(rèn)值是不確定的轴总。
數(shù)據(jù)段:數(shù)據(jù)段(data segment)數(shù)據(jù)段直颅,用來存放已經(jīng)初始化且初始化值為非零的全局變量(包括靜態(tài)變量)。
代碼段:代碼段(code segment/text segment)通常是指用來存放程序執(zhí)行代碼的一塊內(nèi)存區(qū)域怀樟。這部分區(qū)域的大小在程序運行前就已經(jīng)確定功偿,并且內(nèi)存區(qū)域通常屬于只讀, 某些架構(gòu)也允許代碼段為可寫,即允許修改程序往堡。在代碼段中械荷,也有可能包含一些只讀的常數(shù)變量共耍,例如字符串常量等。
堆(heap):堆是用于存放進程運行中被動態(tài)分配的內(nèi)存段吨瞎,它的大小并不固定征堪,可動態(tài)擴張或縮減。當(dāng)進程調(diào)用malloc等函數(shù)分配內(nèi)存時关拒,新分配的內(nèi)存就被動態(tài)添加到堆上(堆被擴張);當(dāng)利用free等函數(shù)釋放內(nèi)存時庸娱,被釋放的內(nèi)存從堆中被剔除(堆被縮減)着绊。
棧(stack):棧又稱堆棧,用戶存放程序臨時創(chuàng)建的局部變量熟尉。在函數(shù)被調(diào)用時归露,其參數(shù)也會被壓入發(fā)起調(diào)用的進程棧中,并且待到調(diào)用結(jié)束后斤儿,函數(shù)的返回值也會被存放回棧中剧包。由于棧的后進先出特點,所以棧特別方便用來保存/恢復(fù)調(diào)用現(xiàn)場往果。
1疆液、棧區(qū)(stack)— 由編譯器自動分配釋放 ,存放函數(shù)的參數(shù)值陕贮,局部變量的值等堕油。其操作方式類似于數(shù)據(jù)結(jié)構(gòu)中的棧。
2肮之、堆區(qū)(heap) — 一般由程序員分配釋放 掉缺, 若程序員不釋放,程序結(jié)束時可能由OS回收 戈擒。注意它與數(shù)據(jù)結(jié)構(gòu)中的堆是兩回事眶明,分配方式倒是類似于鏈表。
3筐高、全局區(qū)(靜態(tài)區(qū))(static)—搜囱,全局變量和靜態(tài)變量的存儲是放在一塊的,初始化的全局變量和靜態(tài)變量在一塊區(qū)域凯傲, 未初始化的全局變量和未初始化的靜態(tài)變量在相鄰的另一塊區(qū)域(BSS)犬辰。 - 程序結(jié)束后由系統(tǒng)釋放
4、文字常量區(qū) — 常量字符串就是放在這里的冰单。 程序結(jié)束后由系統(tǒng)釋幌缝。放在文字常量區(qū)的字符串不可以被修改,而在內(nèi)存堆空間的字符串可以被修改诫欠。
舉個例子
char str1[] = "abcdef";
char str2[] = "abcdef"; // 兩個字符串定義在棧中涵卵,雖然內(nèi)容一樣浴栽,但是地址是不一樣的
char *str1 = "abcdef";
char *str2 = "abcdef"; // 這兩個 str1 == str2 就成立,在文字常量區(qū)轿偎,地址一致典鸡。
5、程序代碼區(qū)— 存放函數(shù)體的二進制代碼坏晦。
int c;
void func(int a){
static int b;
int array[5] = {0};
int *p = (int*)malloc(100);
b = a;
c = b + 1;
}
分析一下萝玷,a 作為參數(shù),和 在函數(shù)體內(nèi)定義的 array 和 指針 p 都是在棧區(qū)昆婿。b 作為靜態(tài)變量球碉,而且沒有被初始化,所以在bss 段仓蛆,同理c是沒有初始化的全局變量睁冬,也是在bss段。
static的作用
C語言中static關(guān)鍵字的作用
【1】static作用的變量
1.static修飾全局變量:在全局變量前加static看疙,全局變量就被定義成為一個全局靜態(tài)變量豆拨。
特點如下:
1)存儲區(qū)(內(nèi)存中的位置):靜態(tài)存儲區(qū)沒變(靜態(tài)存儲區(qū)在整個程序運行期間都存在);
2)初始化:未經(jīng)初始化的全局靜態(tài)變量會被程序自動初始化為0能庆。(自動對象的值是任意的施禾,除非他被顯示初始化)
3)作用域:全局靜態(tài)變量在聲明他的文件之外是不可見的。準(zhǔn)確地講從定義之處開始到文件結(jié)尾搁胆。非靜態(tài)全局變量的作用域是整個源程序(多個源文件可以共同使用)拾积; 而靜態(tài)全局變量則限制了其作用域, 即只在定義該變量的源文件內(nèi)有效丰涉, 在同一源程序的其它源文件中不能使用它拓巧。好處:
1)不會被其他文件所訪問,修改一死;
2)其他文件中可以使用相同名字的變量肛度,不會發(fā)生沖突。
2.static修飾局部變量:在局部變量之前加上關(guān)鍵字static投慈,局部變量就被定義成為一個局部靜態(tài)變量承耿。特點如下:
- 存儲區(qū)(內(nèi)存中的位置):由棧變?yōu)殪o態(tài)存儲區(qū)rodata,生存期為整個源程序伪煤,只能在定義該變量的函數(shù)內(nèi)使用加袋。退出該函數(shù)后,盡管該變量還繼續(xù)存在抱既,但不能使用它;
2)初始化:為靜態(tài)局部變量賦初值是在編譯時進行的职烧,即只賦初值一次,在程序運行時它已有初值。以后每次調(diào)用函數(shù)時不再重新賦初值而只是保留上次函數(shù)調(diào)用結(jié)束時的值蚀之。而為自動變量賦初值蝗敢,不是在編譯時進行的,而是在函數(shù)調(diào)用時進行足删,每調(diào)用函數(shù)重新給一次值寿谴,相對于執(zhí)行一次賦值語句。如果在定義局部變量是不賦初值的話失受,對靜態(tài)局部變量來說讶泰,編譯時自動賦初值0(對數(shù)值型變量)或空字符(對字符型變量)。而對自動變量來說拂到,如果不賦初值峻厚,則它的值是不確定的值。這是由于每次函數(shù)調(diào)用結(jié)束后存儲單元已被釋放谆焊,下次調(diào)用時又重新分配存儲單元,而所分配的單元中的值是不確定的浦夷。
3)作用域:作用域仍為局部作用域辖试,當(dāng)定義它的函數(shù)或者語句塊結(jié)束的時候,作用域隨之結(jié)束劈狐,盡管該變量還繼續(xù)存在罐孝,但不能使用它。
【2】static作用的函數(shù)
在函數(shù)的返回類型前加上關(guān)鍵字static肥缔,函數(shù)就被定義成為靜態(tài)函數(shù)莲兢。
函數(shù)的定義和聲明默認(rèn)情況下是extern的,但靜態(tài)函數(shù)只是在聲明他的文件當(dāng)中可見续膳,不能被其他文件所用改艇。只能被本文件中的函數(shù)調(diào)用,而不能被同一程序其它文件中的函數(shù)調(diào)用坟岔。好處:
1)其他文件中可以定義相同名字的函數(shù)谒兄,不會發(fā)生沖突
2)靜態(tài)函數(shù)不能被其他文件所用。
【3】static在類中的作用 (C++的內(nèi)容社付,這邊也復(fù)習(xí)一下)
1承疲、對于static修飾的成員變量,屬于整個類不屬于單個對象鸥咖, 即使創(chuàng)建多個對象燕鸽,也只為這個靜態(tài)成員變量分配一份內(nèi)存。當(dāng)某個對象修改了這個數(shù)值啼辣,其他對象訪問的時候的數(shù)值也會改變啊研。
2、 static 成員變量的內(nèi)存既不是在聲明類時候分配(也就是在類內(nèi)部禁止初始化),也不是在創(chuàng)建對象的時候分配悲伶。只有在類外初始化時候才會分配(可以選擇賦初值艾恼,或者不賦,不賦初始值默認(rèn)初始化為0)麸锉,也就是說钠绍,沒有在類外初始化的static 成員變量不能使用
3、static 修飾的成員函數(shù)在對象構(gòu)建的時候并不會被傳入 this 指針花沉,這是最大的區(qū)別柳爽。換句話說,static修飾的成員函數(shù)無法訪問類中的其他非靜態(tài)成員碱屁, 因為普通函數(shù)其實就是通過this指針進行了對其他函數(shù)成員的訪問磷脯。
給個具體的程序理解下
#include <iostream>
using namespace std;
class Sample
{
public:
static void Func(Sample* obj);
void Func2();
static int val;
private:
int value;
};
void Sample::Func(Sample* obj) {
//value = 1;
//這個不注釋掉的話,Sample::Func(a); c.Func(a); 兩個調(diào)用都會出錯娩脾,原因就在于沒有this指針
if (obj != NULL) {
obj->value = 1;
cout<<"yes"<<endl;
}
// 但是在這邊赵誓,對于傳入的obj對象,是可以訪問value這個數(shù)值的
}
void Sample::Func2(){
value = 1;
}
int Sample::val = 100; // static 成員變量必須類外定義后才能使用
int main() {
Sample b, c;
Sample *a;
a = &b;
cout<<b.val<<endl;
cout<<Sample::val<<endl;
Sample::Func(a);
c.Func(a);
a->Func2();
return 0;
}
/**
輸出:
100
100
yes
yes
**/
const 和 restrict
const 在 C語言里面的作用:
1柿赊、阻止一個變量被改變 2俩功、申明常量指針和指針常量 3、const修飾形參碰声,表明這個參數(shù)作為輸入只讀诡蜓。
其余的一些作用是針對類的,和C沒啥關(guān)系胰挑。(對于類的成員函數(shù)蔓罚,被const修飾之后這個函數(shù)對成員變量只能讀不能改)
首先 Pointer aliasing 是指兩個或者以上的指針指向同一數(shù)據(jù)。restrict 限定符會告訴編譯器瞻颂,對象已經(jīng)被指針?biāo)貌蛱福荒芡ㄟ^除該指針外所有其他直接或間接的方式修改該對象的內(nèi)容。
值得注意的是贡这,編譯器無法再編譯期檢測兩個指針是否alias核无,是否restrict需要程序員保證。編譯器僅是根據(jù)這個信息優(yōu)化機器碼藕坯。所以基本上只有在非常需要性能和特別明確兩個指針在業(yè)務(wù)邏輯上不會重復(fù)才會用這個限定符团南。
int foo(int *a, int *b) {
*a = 5;
*b = 6;
return *a + *b;
}
在這里面,由于編譯器覺得炼彪,這兩個指針有指向同一塊內(nèi)存的可能吐根,在生成機器碼的時候,會害怕*b = 6
改變了 a 的數(shù)值辐马,所以會在最后求和的時候再讀取一次a的數(shù)值拷橘。
改成
int rfoo(int * restrict a, int * restrict b) {
*a = 5;
*b = 6;
return *a + *b;
}
編譯器通過 restrict 額外信息知道兩個指針不指向同一數(shù)據(jù),在編譯期就能算出返回值,并且直接對 a 和 b 的數(shù)值進行修改冗疮,也就是生成了更優(yōu)化的機器碼萄唇。
extern 關(guān)鍵字
如果想聲明一個變量而非定義它,就在變量名前添加extern關(guān)鍵字术幔,而且不要顯式地初始化變量:
extern int i; //聲明i而非定義
int j; //聲明并定義i
但我們也可以給由extern關(guān)鍵字標(biāo)記的變量賦一個初始值另萤,但這樣就不是一個聲明了,而是一個定義:
extern int v = 2; // 所以一般這種情況下沒必要前面加extern
int v = 2; //這兩個語句效果完全一樣诅挑,都是v的定義
默認(rèn)情況下四敞,一個const對象僅在本文件內(nèi)有效,如果多個文件中出現(xiàn)了同名的const變量時拔妥,其實等同于在不同的文件中分別定義了獨立的變量忿危。
某些時候有這樣一種const變量,它的初始值不是一個常量表達式没龙,但又確實有必要在文件間共享铺厨。這種情況下,我們不希望編譯器為每個文件分別生成獨立的變量硬纤。我們想讓這類const對象像其他非常量對象一樣工作盹沈,也就是說羡鸥,只在一個文件中定義const杉武,而在其他多個文件中聲明并使用它司致。
方法是對于const變量不管是聲明還是定義都添加extern關(guān)鍵字煞躬,這樣只需要定義一次就可以了:
//file1.cpp定義并初始化和一個常量肛鹏,該常量能被其他文件訪問
extern const int bufferSize = function();
//file1.h頭文件
extern const int bufferSize; //與file1.cpp中定義的是同一個
file1.h頭文件中的聲明也由extern做了限定,其作用是指明bufferSize并非本文件獨有恩沛,它的定義將出現(xiàn)在別處在扰。
inline 和 宏定義
宏定義可以理解為純粹和機械的文本替換。
內(nèi)聯(lián)函數(shù)的調(diào)用和宏函數(shù)有點類似雷客,他在調(diào)用點會將代碼展開芒珠,而不是開辟函數(shù)棧,用看目標(biāo)代碼量的增加換時間搅裙。最大的區(qū)別是宏定義編譯器一定會給你作替換皱卓,但是inline函數(shù)僅僅是一個對編譯器的建議,所以最后能否真正內(nèi)聯(lián)部逮,看編譯器的意思:它如果認(rèn)為函數(shù)不復(fù)雜娜汁,能在調(diào)用點展開,就會真正內(nèi)聯(lián)兄朋,并不是說聲明了內(nèi)聯(lián)就會內(nèi)聯(lián)掐禁,聲明內(nèi)聯(lián)只是一個建議而已。所以一般來講inline函數(shù)內(nèi)部不允許包含復(fù)雜的控制結(jié)構(gòu)語句,特別是循環(huán)和開關(guān)語句傅事。在C語言里面缕允,inline函數(shù)一般會放在頭文件里面(因為內(nèi)聯(lián)函數(shù)要在調(diào)用點展開,所以編譯器必須隨處可見內(nèi)聯(lián)函數(shù)的定義蹭越,要不然就成了非內(nèi)聯(lián)函數(shù)的調(diào)用了)障本,并且是static inline,因為如果編譯器覺得這個函數(shù)太復(fù)雜不能內(nèi)聯(lián)般又,就會把這個函數(shù)進行編譯并留存符號位彼绷。static目的是讓該關(guān)鍵字標(biāo)識的函數(shù)只在本地文件可見,同一個程序的其它文件是不可見該函數(shù)的.換句話說,就算你其它文件里包含了同名同參數(shù)表的函數(shù)定義的話,也是不會引起函數(shù)重復(fù)定義的錯誤的.因為static是僅在當(dāng)前文件可見.
不同點:
1、 編譯器會對內(nèi)聯(lián)函數(shù)的參數(shù)類型做安全檢查或自動類型轉(zhuǎn)換茴迁,而宏定義則不會寄悯,他只是進行單純的文字替換,不會做任何類型的檢查
2堕义、內(nèi)聯(lián)函數(shù)在運行時可調(diào)試猜旬,而宏定義不可以。
3倦卖、內(nèi)聯(lián)函數(shù)可以訪問類的成員變量洒擦,宏定義則不能。
在類中聲明同時定義的成員函數(shù)怕膛,自動轉(zhuǎn)化為內(nèi)聯(lián)函數(shù)熟嫩。
關(guān)于宏定義的冷僻知識:
宏名在源程序中若用引號括起來,則預(yù)處理程序不對其作宏代換褐捻。(對 )
#符號是把宏的參數(shù)直接替換為字符串掸茅,等價于把參數(shù)加上雙引號
#用在預(yù)編譯語句里面可以把預(yù)編譯函數(shù)的變量直接格式成字符
而##把兩個宏參數(shù)貼合在一起
注意:當(dāng)宏參數(shù)是另一個宏的時候,需要注意的是凡宏定義里有用’#’或’##’的地方宏參數(shù)是不會再展開.
#define INT_MAX 0x7FFFFFFF
#define A 2
#define STR() #s
#define CONS(a, b) (int)(a##e##b)
int main()
{
printf("int max: %s\n", STR(INT_MAX));
printf("%d\n", CONS(A, A)); // compile error --- int(AeA)
return 0;
}
/**
兩句print會被展開為
printf("int max: %s\n","INT_MAX");――打印結(jié)果為
printf("%s\n", int(AeA));―――編譯錯誤柠逞,找不到AeA
由于A和INT_MAX均是宏昧狮,且作為宏CONS和STR的參數(shù),并且宏CONS和STR中均含有#或者##符號板壮,所以A和INT_MAX均不能被解引用逗鸣。導(dǎo)致不符合預(yù)期的情況出現(xiàn)。
**/
__attribute__
GNU C 的一大特色就是__attribute__
機制绰精。__attribute__
可以設(shè)置函數(shù)屬性(Function Attribute )撒璧、變量屬性(Variable Attribute )和類型屬性(Type Attribute )。
__attribute__
語法格式為:__attribute__ ((attribute-list))
對于函數(shù)屬性來講笨使,有 section 這個屬性沪悲。關(guān)鍵字會將被修飾的變量或函數(shù)編譯到特定的一塊位置,不是物理存儲器上的特定位置阱表,而是在可執(zhí)行文件的特定段內(nèi)殿如。
指針和函數(shù)指針
int (*p[10])(int*)
首先[]的優(yōu)先級大于*贡珊,那么p是一個數(shù)組。右邊看我們可以知道這是個函數(shù)指針涉馁,參數(shù)是 int*
门岔,左邊看 開頭是 int*
表明了這個函數(shù)的返回值是個int指針。所以p是一個函數(shù)指針數(shù)組烤送,存儲著函數(shù)地址寒随。
舉一反三:
int (*p)[10](int*)
: p是一個指向函數(shù)數(shù)組的指針,函數(shù)的返回值類型為int帮坚,參數(shù)列表里有一個 int*
參數(shù)妻往。
int *p[10]
: []的優(yōu)先級高于*,因此p首先和[] 結(jié)合试和,表示p是一個10個元素的數(shù)組讯泣,int *p[10]
其實等價 int* p[10]
(這么看可能就更明顯一點,表面p這個數(shù)組里的元素類型是int*
)阅悍。
int (*p)[10]
: 這邊使用了括號強行改變了運算符優(yōu)先級好渠,p先與*結(jié)合,所以p首先是個指針节视,然后看p指針的指向拳锚,指向的是一個int 類型的10個元素的數(shù)組。
int *(*p)[4]
: 有了上面的分析寻行,就能得出霍掺,p首先與*結(jié)合是個指針,指向一個int*
類型且大小為10的數(shù)組拌蜘。
函數(shù)類型的指針杆烁,可以這么理解,對于函數(shù)拦坠,描述的要素有:返回值類型 參數(shù)個數(shù)及其類型
#include <stdio.h>
#include <stdlib.h>
typedef int (*milaoshu)(int a, int b);
int func(int a, int b){
return a+b;
}
int main() {
int a = 10;
int b = 20;
printf("a+b = %d\n", a+b);
milaoshu p; //如果不給這個類型起名叫米老鼠,那么在聲明變量p的時候應(yīng)該這樣聲明 int (*p)(int, int);
p = func;
int c = p(a ,b);
printf("a+b = %d\n", c);
return 0;
}
C-string
C++里面有string類连躏,C風(fēng)格字符串其實就是個字符數(shù)組剩岳,這個數(shù)組還需要以 '\0' 作為字符串結(jié)束的標(biāo)記符贞滨。
易錯點一:
對于字符串指針的操作。正確的初始化方式
char test[10] = {'0'};
char test[10] = {'\0'};
錯誤的方式
char test[10] = {0};
//如果非要用整數(shù)0初始化:signed char 和 unsigned char才被視作整數(shù)類型
signed char test[10] = {0};
易錯點二:
char *test = "abc"; // 會創(chuàng)建一個字符串常量拍棕,"abc"在靜態(tài)存儲區(qū)晓铆,不可更改; test 在棧上
const char *test = "abc"; // 更好的寫法是用const額外進行注明。
// 如果希望在堆上分配一個初始化字符串?dāng)?shù)組
char test[] = "abc";
char test[] = {'a', 'b', 'c', '\0'};
test[1] = 'e'; // 這時候這句話不會報錯
對于字符串指針的初始化绰播,務(wù)必在堆上開辟足夠空間骄噪,再用strcpy,memcpy 或者其他安全函數(shù)進行操作蠢箩。
char *str;
str = (char *)malloc(sizeof(char) * 5);
strcpy(str, "abcd");
str[2] = 'e';
printf("%s", str);
C里面處理字符串時候比較惡心的問題:
-
C里面<string.h>的
strcpy
strcat
等 函數(shù)链蕊,一定需要注意開辟足夠大的空間拷貝或者擴長字符串(記得是 strlen(str) + 1) 事甜。如果目標(biāo)數(shù)組 dest 不夠大,而源字符串的長度又太長滔韵,可能會造成緩沖溢出的情況 逻谦。char *strcpy(char *dst, const char *src);
并沒有長度檢測除了對長度進行檢查:
(1)const 修飾:源字符串參數(shù)用const修飾,防止修改源字符串陪蜻;
(2)空指針檢查:源指針和目的指針都有可能會出現(xiàn)空指針的情況邦马,所以應(yīng)該對其進行檢查;
strncpy
和strncat
等字符串操作函數(shù)很容易產(chǎn)生結(jié)束符丟失的問題宴卖。操作完記得補結(jié)束符滋将。memcpy
與memmove
的目的都是將N個字節(jié)的源內(nèi)存地址的內(nèi)容拷貝到目標(biāo)內(nèi)存地址中,源內(nèi)存和目標(biāo)內(nèi)存如果存在重疊症昏,會引發(fā)錯誤随闽。這時候只有memmove_s
能正確實施拷貝。
所以一般這類函數(shù)都有strcpy_s
等安全版本
malloc和free
首先malloc 和 free 要一一對應(yīng)齿兔,第二是 malloc 的大小參數(shù)要做數(shù)據(jù)檢查橱脸,0字節(jié)的長度申請,或者負(fù)數(shù)長度去申請分苇,負(fù)數(shù)會被當(dāng)成一個很大的無符號整數(shù)添诉,從而申請內(nèi)存大小過大導(dǎo)致錯誤。
malloc 開辟二維數(shù)組的示例医寿。leetcode 122栏赴。
int maxProfit(int* prices, int pricesSize){
int **dp = (int**)malloc(pricesSize*sizeof(int*));
for (int i = 0; i < pricesSize; i++) {
dp[i] = (int*)malloc(2*sizeof(int));
}
dp[0][0] = 0; dp[0][1] = -prices[0];
for (int i = 1; i < pricesSize; i++) {
dp[i][0] = fmax((dp[i-1][1] + prices[i]), dp[i-1][0]);
dp[i][1] = fmax((dp[i-1][0] - prices[i]), dp[i-1][1]);
}
int ans = fmax(dp[pricesSize - 1][0], dp[pricesSize - 1][1]);
for (int i = 0; i < pricesSize; i++) {
free(dp[i]);
}
free(dp);
return ans;
}
C語言的格式化函數(shù)
常見的就是scanf 和 printf,針對標(biāo)準(zhǔn)輸入輸出流進行操作靖秩。
其他的包括輸入目的地為文件的(fprintf)须眷,輸入 fp 指定的輸出流。printf()函數(shù)可以視為 fprintf()的特殊版本沟突。
int fprintf(FILE *restrict fp, const char * restrict format...);
輸入目的地為字符串的 (sprintf)花颗。將格式化數(shù)據(jù)寫入 buf 指向的 char 數(shù)組,并在后面加上一個標(biāo)志結(jié)尾的空字符惠拭。(這類涉及到字符數(shù)組空間的大多都有問題)
int sprintf(char * restrict buf,
const char * restrict format...);
在上述函數(shù)原型中出現(xiàn)的省略號(...)扩劝,表示還可有更多參數(shù),但這些參數(shù)是可選的职辅。還有一些 printf()函數(shù)系列需要一個指針參數(shù)棒呛,以指向一個參數(shù)列表,而不是在函數(shù)調(diào)用時直接接收數(shù)量可變的參數(shù)域携。這些函數(shù)的名稱都以一個 v 開始簇秒,表示“variable argument list”(可變參數(shù)列表)的意思, 如果想使用支持可變參數(shù)列表的函數(shù)秀鞭,除了頭文件 <stdio.h> 以外趋观,還必須包含頭文件 <stdarg.h> :
int vprintf( const char * restrictformat, va_list argptr );
int vfprintf( FILE * restrict fp, const char * restrict format,
va_list argptr );
int vsprintf( char * restrict buf, const char * restrict format,
va_list argptr );
int vsnprintf( char * restrict buffer, size_t n,
const char * restrict format, va_list argptr );
C11 標(biāo)準(zhǔn)為這些函數(shù)都提供了一個新的“安全”的版本扛禽。這些對應(yīng)的新函數(shù)均以后綴_s(例如,fprintf_s())皱坛。新函數(shù)測試它們接收的所有指針參數(shù)是否為空指針旋圆。
格式化字符串定義了數(shù)據(jù)的輸出格式,并包含了一些普通字符和轉(zhuǎn)換說明(conversion specification)麸恍。每個轉(zhuǎn)換說明都定義了函數(shù)該如何將可選參數(shù)轉(zhuǎn)換并格式化灵巧,以供輸出。
轉(zhuǎn)換說明的通用格式如下:
%[標(biāo)記][字段寬度][.精度][長度修飾符]修飾符
字符寬度指定對應(yīng)的數(shù)據(jù)項所輸出的最少字符數(shù)量抹沪。默認(rèn)情況下刻肄,字段中的被轉(zhuǎn)換數(shù)據(jù)為右對齊(right-justified),左邊多的位置用空格填補融欧。如果標(biāo)記包含減號(-)敏弃,則為左對齊(left-justified),超出的字段寬度采用空格向右填補噪馏。
printf("1234567890123456\n"); // 用來觀察字符長度
printf( "%-10s %s\n", "Player", "Score" ); // 表頭
printf( "%-10s %4d\n", "John", 120 ); // 字段寬度:10麦到;4
printf( "%-10s %4d\n", "Mary", 77 );
//會生成一個簡單的表格,120 和 77 占四位欠肾,右對齊
1234567890123456
Player Score
John 120
Mary 77
如果字段是右對齊的瓶颠,可以采用 0 而非空格填充。要實現(xiàn)這樣的效果刺桃,在轉(zhuǎn)換說明標(biāo)記中包括一個 0(指數(shù)字零)粹淋。下面的例子以 mm-dd-yyyy 的格式輸出日期:
int month = 5, day = 1, year = 1987;
printf( "Date of birth: %02d-%02d-%04d\n", month, day, year );
輸出:
Date of birth: 05-01-1987
列舉下控制輸出格式的控制符:
轉(zhuǎn)換修飾符 | 說明 |
---|---|
%d | 按十進制整型數(shù)據(jù)的實際長度輸出。 |
%p | 輸出地址數(shù)據(jù)瑟慈。 |
%ld | 輸出長整型數(shù)據(jù)桃移。 |
%md | m 為指定的輸出字段的寬度。如果數(shù)據(jù)的位數(shù)小于 m葛碧,則左端補以空格借杰,若大于 m,則按實際位數(shù)輸出进泼。 |
%u | 輸出無符號整型(unsigned)蔗衡。輸出無符號整型時也可以用 %d,這時是將無符號轉(zhuǎn)換成有符號數(shù)缘琅,然后輸出粘都。但編程的時候最好不要這么寫廓推,因為這樣要進行一次轉(zhuǎn)換刷袍,使 CPU 多做一次無用功。 |
%c | 用來輸出一個字符樊展。 |
%f | 用來輸出實數(shù)呻纹,包括單精度和雙精度堆生,以小數(shù)形式輸出。不指定字段寬度雷酪,由系統(tǒng)自動指定淑仆,整數(shù)部分全部輸出,小數(shù)部分輸出 6 位哥力,超過 6 位的四舍五入蔗怠。 |
%.mf | 輸出實數(shù)時小數(shù)點后保留 m 位,注意 m 前面有個點吩跋。 |
%o | 以八進制整數(shù)形式輸出寞射,相比十六進制和十進制用的很少。 |
%s | 用來輸出字符串锌钮。用 %s 輸出字符串但是此時要先定義字符數(shù)組或字符指針存儲或指向字符串桥温。 |
%x(或 %X 或 %#x 或 %#X) | 以十六進制形式輸出整數(shù),如果是小寫的x 梁丘,輸出的字母就是小寫的侵浸;如果是大寫的X ,輸出的字母就是大寫的氛谜;如果加一個# 掏觉,就以標(biāo)準(zhǔn)的十六進制形式輸出。 |
C 不同變量的內(nèi)存占用
- static變量在靜態(tài)區(qū)值漫,對于包含static變量的實例履腋,sizeof均不納入計算
- 不同編輯器的默認(rèn)對齊大小不一樣,gcc是4字節(jié)對齊
- 編輯器規(guī)定n字節(jié)對齊的pack指令 #pragma pack(n)
- 在編譯階段處理惭嚣,sizeof作用范圍內(nèi)的內(nèi)容不能被編譯遵湖,所以sizeof()內(nèi)的運算不被執(zhí)行
- sizeof(函數(shù))=sizeof(返回值類型)
- sizeof和strlen:sizeof計算字符串容量,算’\0’晚吞,strlen計算字符串長度延旧,到’\0’截止
- 對于C++的string類,string的實現(xiàn)在各庫中可能有所不同槽地,但是在同一庫中相同一點是迁沫,無論你的string里放多長的字符串,它的sizeof()都是固定的捌蚊,字符串所占的空間是從堆中動態(tài)分配的集畅,與sizeof()無關(guān)。 sizeof(string)=4可能是最典型的實現(xiàn)之一缅糟,不過也有sizeof()為12挺智、32字節(jié)的庫實現(xiàn)。
32/64 位 系統(tǒng)各類型內(nèi)存大小對照(有無unsigned修飾都一樣窗宦,大小單位字節(jié)byte):
系統(tǒng) | 32位 | 64位 |
---|---|---|
char | 1 | 1 |
short | 2 | 2 |
int | 4 | 4 |
float | 4 | 4 |
long | 4 | 8 |
*(地址) | 4 | 8 |
double | 8 | 8 |
long long | 8 | 8 |
對于不同系統(tǒng)赦颇,有細(xì)致的不一樣的地方二鳄,32位和64位系統(tǒng)在Windows下基本數(shù)據(jù)類型的大小都是一樣的。只有指針的大小不一樣媒怯!32位指針大小為4byte订讼,而64位的指針大小為8byte。
Linux下扇苞,long型是64位的欺殿,這一點是和Windows不同的地方。(表中是Linux標(biāo)準(zhǔn)的long)
C語言位運算
首先位運算最多的肯定是左移和右移操作鳖敷。
左移
對于左移祈餐,丟棄最高位,低位補0哄陶, 左移 n 位帆阳, 相當(dāng)于乘以 。譬如 二進制的 000...001屋吨,左移兩位 就是 000... 100蜒谤。
對于有符號數(shù),左移可能會導(dǎo)致符號位變化導(dǎo)致的溢出至扰。
int是有符號的整形數(shù)鳍徽,左端的1位是符號為,即0為正1為負(fù)敢课,那么用移位的時候就會出現(xiàn)溢出阶祭,如:
int i=0x40000000;//16進制的40000000,為2進制的01000000...0000
i=i<<1;
//那么直秆,i在移動1位之后就會變成0x80000000濒募,也就是2進制的100000...0000,符號位被置1圾结,起位全是0瑰剃,變成了int類型所能表達的最小值,32為的int這個值是-2147483648筝野,溢出晌姚,如果在接著把i左移1位會出現(xiàn)什么情況呢?在c語言都采用了丟棄最高位的處理方法,丟棄了1之后歇竟,i的值變成了0
其他的左移特殊情況是挥唠,當(dāng)左移的位數(shù)超過該數(shù)值類型的最大位數(shù)時候,編譯器會用左移的位數(shù)模類型的最大位數(shù)焕议,例如 對于32 位 類型宝磨,左移33位,這時候其實就相當(dāng)于只左移了一位。
int i=1,j=0x80000000;//設(shè)int為32位
i=i<<33;//33%32=1 左移動1位懊烤,i變成2
j=j<<33;//33%32=1 左移動1位,j變成0宽堆,最高為被丟棄
右移
對于右移腌紧,對符號位的處理和左移不同,對于有符號整數(shù)來說,比如int類型,右移會保持符號位不變。
具體操作畜隶,符號位向右移動后壁肋,本來是正數(shù)的會在最前面會補0,本來是負(fù)數(shù)的會在最前面補1籽慢,以此保持符號位的不變浸遗。(注意負(fù)數(shù)右移往往會變得非常小)也就是匯編語言中的算術(shù)右移.同樣當(dāng)移動的位數(shù)超過類型的長度時,會取余數(shù),然后移動余數(shù)個位箱亿。
結(jié)構(gòu)體和聯(lián)合體的內(nèi)存計算
區(qū)別:結(jié)構(gòu)體的各個成員會占用不同的內(nèi)存塊跛锌,互相之間沒有影響;而聯(lián)合體的所有成員占用同一段內(nèi)存塊届惋,修改其中一個成員會對整個內(nèi)存塊保存的值產(chǎn)生影響髓帽。
約定為32位系統(tǒng),即char 1字節(jié)脑豹、short 2字節(jié)郑藏、int 4字節(jié)
每個特定平臺上的編譯器都有自己的默認(rèn)“對齊系數(shù)”(也叫對齊模數(shù))。gcc中默認(rèn)#pragma pack(4)瘩欺,可以通過預(yù)編譯命令#pragma pack(n)必盖,n = 1,2,4,8,16來改變這一系數(shù)。
有效對齊值:是給定值#pragma pack(n)和結(jié)構(gòu)體中最長數(shù)據(jù)類型長度中較小的那個俱饿。有效對齊值也叫對齊單位歌粥。
該問題總結(jié)為兩條規(guī)律:
- 結(jié)構(gòu)體第一個成員的偏移量(offset)為0,以后每個成員相對于結(jié)構(gòu)體首地址的 offset 都是該成員大小與有效對齊值中較小那個的整數(shù)倍拍埠,如有需要編譯器會在成員之間加上填充字節(jié)阁吝。
- 結(jié)構(gòu)體的總大小為 有效對齊值 的整數(shù)倍,如有需要編譯器會在最末一個成員之后加上填充字節(jié)械拍。
struct A{
char a; //1
int b; //空3 + 4 = 7 (規(guī)則1)
short c; //2+空2=4 (規(guī)則2)
};
struct B{
char a; //1
short b; //空1 + 2 = 3 (規(guī)則1)
int c; //4
};
struct C{ //在四字節(jié)對齊的時候突勇,由于最長就是short int,所以有效對齊值為2
short int num; // 2
char sex; // 1
short int data; // 空1 + 2 = 3 (規(guī)則1)
char aa; // 1 + 空1 (規(guī)則2)
};
如果指定了對齊值1坷虑,那么就是所有的元素加起來的大小甲馋。
#pragma pack(1)
struct A{
char a; //1
int b;//4
short c;//2
};
#pragma pack(1)
struct B{
char a;//1
short b;//4
int c;//2
};
這時候結(jié)構(gòu)體A和B的大小都為7
如果結(jié)構(gòu)體里面包含聯(lián)合體呢∑穑看之前聯(lián)合體的定義定躏,聯(lián)合體中的元素公用同一塊內(nèi)存,所以聯(lián)合體共用一塊內(nèi)存,其內(nèi)存大小為最大成員的內(nèi)存大小痊远,所以所有成員的地址都一樣垮抗;給聯(lián)合體某個成員賦值時會影響到另外一個成員的數(shù)值。
struct LinkNode {
char ch;
char *ptr;
union {
short a;
short b;
unsigned int c : 2;
unsigned int d : 1;
}
struct LinkNode *next;
}
所以上面的計算是 1 + (空3 + 4)+ 4 + 4 = 16
在這個知識點中還會涉及位域的概念碧聪。有些信息在存儲時冒版,并不需要占用一個完整的字節(jié),而只需占幾個或一個二進制位逞姿。例如在存放一個開關(guān)量時辞嗡,只有 0 和 1 兩種狀態(tài),用 1 位二進位即可滞造。區(qū)分是位域還是結(jié)構(gòu)體或者聯(lián)合體就是看里面在規(guī)定時候有沒有使用 ":" 進行規(guī)定元素的大小续室。
struct 位域結(jié)構(gòu)名
{
位域列表
};
struct bs{
int a:8;
int b:2;
int c:6;
}data;
說明 data 為 bs 變量,共占兩個字節(jié)谒养。其中位域 a 占 8 位挺狰,位域 b 占 2 位,位域 c 占 6 位买窟。
點運算符和箭頭運算符
這兩個都是用于成員變量的獲取她渴,只看C的話基本就是對結(jié)構(gòu)體變量或者結(jié)構(gòu)體指針的操作。之前也比較明晰的是點運算符用于結(jié)構(gòu)體對象蔑祟,箭頭運算符對應(yīng)的是結(jié)構(gòu)體指針趁耗。
實際運用中有點小細(xì)節(jié)。對于我們開辟的一個結(jié)構(gòu)體數(shù)組疆虚,例如
struct Article arrArticle[10];
arrArticle苛败,是一個指向第一個數(shù)組元素的常量指針。所以 arrArticle->number 指向第一個數(shù)組元素的成員 number径簿。簡單地說罢屈,對于任一的索引值 i,下面 3 個表達式是等價的:(注意按照下標(biāo)訪問和數(shù)組基指針進行加減訪問在成員訪問運算符選擇上的區(qū)別)
arrArticle[i].number
(arrArticle+i)->number
(*(arrArticle+i)).number
C的一些常用標(biāo)準(zhǔn)庫函數(shù)
memset()
該庫函數(shù)屬于C庫函數(shù) <string.h>篇亭。復(fù)制字符 c(一個無符號字符)到參數(shù) str 所指向的字符串的前 n 個字符缠捌。
void *memset(void *str, int c, size_t n)
雖然本意是用于字符串,但是也支持對int初始數(shù)組進行賦初值, 一般賦值全為0译蒂, 或者用 0x3f填滿曼月,達到構(gòu)造類似于INT_MAX的功能。例如使用 0x3f進行賦值柔昼,stack里面的每個int數(shù)值被 初始化為0x3f3f3f3f哑芹,即為1061109567。
int stack[10];
memset(stack, 0, sizeof(stack)); // memset(stack, 0x3f, sizeof(stack))
for (int i = 0; i < 10; i++) {
printf("%d ", stack[i]);
}
如果是直接進行初始值的定義使用花括號捕透。
int arr[2][3] = {{1,2,3}, {4,5,6}}
memcpy_s ()
memcpy_s(dest, destMax, src, srcLen);
接收數(shù)組長度變量的destMax 必須設(shè)置為 sizeof(dest)
或者 BUFF_SIZE*sizeof(elementType)
聪姿。只有當(dāng)元素數(shù)組為字節(jié)類型(如char碴萧,signed char,unsigned char) 時末购,sizeof(elementType)
為1破喻,此時可以忽略。
qsort()
void qsort(void *base, size_t nitems, size_t size, int (*compar)(const void *, const void*))
base -- 指向要排序的數(shù)組的第一個元素的指針盟榴。
nitems -- 由 base 指向的數(shù)組中元素的個數(shù)曹质。
size -- 數(shù)組中每個元素的大小,以字節(jié)為單位曹货。
compar -- 用來比較兩個元素的函數(shù)咆繁。
#include <stdio.h>
#include <stdlib.h>
int values[] = { 88, 56, 100, 2, 25 };
int cmpfunc (const void * a, const void * b)
{
return ( *(int*)a - *(int*)b );
}
int main()
{
int n;
printf("排序之前的列表:\n");
for( n = 0 ; n < 5; n++ ) {
printf("%d ", values[n]);
}
qsort(values, 5, sizeof(int), cmpfunc);
printf("\n排序之后的列表:\n");
for( n = 0 ; n < 5; n++ ) {
printf("%d ", values[n]);
}
return(0);
}
解釋下比較函數(shù)的寫法:
在一些函數(shù)定義中讳推,const void *a
這類形參顶籽,const void *a
這是定義了一個指針,a可以指向任意類型的值银觅,但它指向的值必須是常量礼饱,在這種情況下,我們不能修改被指向的對象究驴,但可以使指針指向其他對象镊绪。void *
則為“無類型指針”,void *
可以指向任何類型的數(shù)據(jù)洒忧。
后面 *(int*) a
分為兩步 (int*) a
是把上面的無類型指針轉(zhuǎn)換為 int 型指針蝴韭, 再前面加上 *
變成 *(int*) a
是進行了一個dereference解引用。
不過其實傳參也可以直接傳個 int 類型的指針熙侍。
int cmpfunc(int* a, int* b) {
return *a - *b;
}
對于double類型的比較需要特別注意榄鉴。
double in[100];
int cmp( const void *a , const void *b)
{
return *(double *)a > *(double *)b ? 1 : -1;
}
qsort(in,100,sizeof(in[0]),cmp);
借助 strcmp對字符串進行排序
struct In
{
int data;
char str[100];
}s[100];
//按照結(jié)構(gòu)體中字符串str的字典順序排序
int cmp ( const void *a , const void *b)
{
return strcmp( (*(In *)a).str , (*(In *)b).str);
}
qsort(s,100,sizeof(s[0]),cmp);
除此之外蛉抓,qsort 也比較適用于對結(jié)構(gòu)體進行排序庆尘。
struct In
{
int x;
int y;
}s[100];
//按照x從小到大排序,當(dāng)x相等時按照y從大到小排序
int cmp( const void *a , const void *b )
{
struct In *c = (In *)a;
struct In *d = (In *)b;
if(c->x != d->x) return c->x - d->x;
else return d->y - c->y;
}
qsort(s,100,sizeof(s[0]),cmp);
fgets()
C 庫函數(shù) char *fgets(char *str, int n, FILE *stream)
從指定的流 stream 讀取一行巷送,并把它存儲在 str 所指向的字符串內(nèi)驶忌。當(dāng)讀取 (n-1) 個字符時,或者讀取到換行符時笑跛,或者到達文件末尾時付魔,它會停止,具體視情況而定飞蹂。
- str -- 這是指向一個字符數(shù)組的指針抒抬,該數(shù)組存儲了要讀取的字符串。
- n -- 這是要讀取的最大字符數(shù)(包括最后的空字符)晤柄。通常是使用以 str 傳遞的數(shù)組長度擦剑。
- stream -- 這是指向 FILE 對象的指針璧帝,該 FILE 對象標(biāo)識了要從中讀取字符的流层坠。
如果成功,該函數(shù)返回相同的 str 參數(shù)。如果到達文件末尾或者沒有讀取到任何字符板辽,str 的內(nèi)容保持不變,并返回一個空指針据忘。如果發(fā)生錯誤惨险,返回一個空指針。
C和C++的一些區(qū)別
沒有引用的概念售担,需要二級指針
C里沒有引用赁遗,C++里才有,也就沒有引用傳值之類的族铆。所以會涉及一些二級指針的概念岩四。對函數(shù)來說,它所傳遞的任何參數(shù)僅僅是原來參數(shù)的一個拷貝哥攘。C語言里剖煌,改變值只能通過指針(地址)方式進行傳遞,傳遞數(shù)組也可以改變值逝淹,但實際上耕姊,傳遞數(shù)組就是傳遞指針,也就是默認(rèn)傳入了數(shù)組的頭元素指針栅葡。
摘引博客給出一個樣例
首先給出一個錯誤代碼
#include <stdio.h>
#include <malloc.h>
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
void InitLinkList(LNode *L)
{
L=(LNode *)malloc(sizeof(LNode));
L->data=0;
L->next=NULL;
}
int main()
{
LNode *L=NULL;
InitLinkList(L);
printf("%p\n",L);
return 0;
}
錯誤點一:該InitLinkList并不能真正初始化一個鏈表頭結(jié)點茉兰,在函數(shù)里我們的確是給L分配了內(nèi)存,初始化了結(jié)點欣簇,但是主函數(shù)里沒有把指針L本身的地址傳遞進去规脸,InitLinkList()里的L并不是main()里的L,雖然名稱是一樣的醉蚁,但是InitLinks()的L是局部的(所以燃辖,其實你寫成a,b,c,d都沒關(guān)系),傳進來的只是一個LNode*副本网棍,這個副本和外面的L的內(nèi)容是一樣的黔龟,但是變量不是同一個,當(dāng)這個子函數(shù)執(zhí)行完后滥玷,main()里的L還是原來的L氏身。
錯誤點二:沒有釋放malloc的內(nèi)存,在InitLinkList函數(shù)中通過malloc分配的內(nèi)存是通過堆來劃分的惑畴,這意味著函數(shù)調(diào)用完畢后蛋欣,內(nèi)存不能自動釋放,將會造成內(nèi)存泄漏如贷。
第一個修改方案是直接利用return返回值進行對main函數(shù)中L的賦值陷虎。
#include <stdio.h>
#include <malloc.h>
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
LNode * InitLinkList(LNode *L)
{
L=(LNode *)malloc(sizeof(LNode));
L->data=0;
L->next=NULL;
return L;
}
int main()
{
LNode *L=NULL;
L=InitLinkList(L);
printf("%p\n",L);
return 0;
}
要么就是修正主函數(shù)里面沒有直接傳入L的地址的錯誤到踏,在功能函數(shù)參數(shù)中傳入一個二級指針。
#include <stdio.h>
#include <malloc.h>
typedef struct LNode
{
int data;
struct LNode *next;
}LNode;
void InitLinkList(LNode **L)
{
(*L)=(LNode *)malloc(sizeof(LNode));
(*L)->data=0;
(*L)->next=NULL;
}
int main()
{
LNode *L=NULL;
InitLinkList(&L);
printf("%p\n",L);
return 0;
}
理解二級指針的關(guān)鍵在于尚猿,在main中L本身就是一個指針窝稿,指向某塊內(nèi)存。取L這個變量的地址作為實參內(nèi)容傳遞給initLinkList()函數(shù)里的形參凿掂,形參就得用指針的指針來存儲這個地址變量伴榔。那么子函數(shù)里面的L的內(nèi)容不就是main()里L(fēng)地址么,那么庄萎,*L
不就是main里L(fēng)的內(nèi)容么踪少,也就是說,對*L
操作就是對main()里的L進行操作糠涛。
如果是數(shù)組傳參援奢,會簡單些,我們可以看看傳參上的區(qū)別脱羡。
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
typedef struct{
int x;
}test_struct;
void init(test_struct* input){
input->x = 1;
input[1].x = 2;
input[2].x = 3;
}
int main() {
test_struct *slist = (test_struct*)malloc(sizeof(test_struct)*3);
init(slist);
for (int i = 0; i < 3; i++) {
printf("val: %d \n", slist[i].x);
}
return 0;
}
復(fù)習(xí)下之前的內(nèi)容:對于我們開辟的一個結(jié)構(gòu)體數(shù)組萝究,例如
struct Article arrArticle[10];
arrArticle免都,是一個指向第一個數(shù)組元素的常量指針锉罐。所以 arrArticle->number 指向第一個數(shù)組元素的成員 number。簡單地說绕娘,對于任一的索引值 i脓规,下面 3 個表達式是等價的:(注意按照下標(biāo)訪問和數(shù)組基指針進行加減訪問在成員訪問運算符選擇上的區(qū)別)
arrArticle[i].number
(arrArticle+i)->number
(*(arrArticle+i)).number
struct 和 typedef struct
在C++ 中, 雖然結(jié)構(gòu)體用的也不多险领,結(jié)構(gòu)體的定義模式是這樣的:
struct Student{
int name;
};
于是就定義了結(jié)構(gòu)體類型Student侨舆,聲明變量時直接Student stu1;
在C里面绢陌,定義結(jié)構(gòu)體的寫法
typedef struct Student
{
int name;
}Stu;
于是在聲明變量的時候就可:Stu stu1;(如果沒有typedef就必須用struct Student stu1;來聲明)這里的Stu實際上就是struct Student的別名挨下。
在C++里面也可以附加typedef進行別名的設(shè)置。
struct Student
{
int name;
}stu1; // 這邊的stu1是一個變量
typedef struct Student2
{
int name;
}stu2; // 這邊就是一個別名
uthash
uthash 作為一個頭文件脐湾〕舭剩可以通過添加UT_hash_handle
把任意一個C結(jié)構(gòu)體中的一個或者多個數(shù)值組合起來作為key達到hash的功能。
初始化
1秤掌、uthash需要自定義數(shù)據(jù)結(jié)構(gòu)愁铺, 一個包含UT_hash_handle hh
的結(jié)構(gòu)體。
2闻鉴、結(jié)構(gòu)體中需要定義key的數(shù)據(jù)類型和其余作為val的數(shù)據(jù)類型茵乱。
struct my_struct {
int id;
char name[10];
UT_hash_handle hh;
};
在定義后需要初始化一個空指針指向定義的hash結(jié)構(gòu)孟岛。這里的hash表結(jié)構(gòu)類似于一個雙向鏈表瓶竭,這邊設(shè)置的這個指針就類似于鏈表的頭結(jié)點督勺。
struct my_struct *users = NULL;
之后為hash表結(jié)構(gòu)開辟合適的空間,再進行添加item的操作斤贰。
添加
對應(yīng)不同的key的數(shù)據(jù)類型玷氏,需要使用不同的添加函數(shù)。
- key是int腋舌,可以使用 HASH_ADD_INT
- key是字符串盏触,可以使用 HASH_ADD_STR
- key是指針,可以使用 HASH_ADD_PTR
- 其它,可以使用 HASH_ADD块饺,上述實際都是調(diào)用這個方法赞辩,不過簡化了參數(shù)
在上面已經(jīng)使用過HASH_ADD_INT
, 第一個參數(shù)是之前定義的結(jié)構(gòu)體空指針,第二個參數(shù)申明了會使用結(jié)構(gòu)體中的哪部分作為可以hash的鍵值授艰,第三個參數(shù)就是我們上面完成賦值初始化構(gòu)建出的結(jié)構(gòu)體實例s辨嗽。
void add_user(int user_id, char *name) {
struct my_struct *s;
s = malloc(sizeof(struct my_struct));
s->id = user_id;
strcpy(s->name, name);
HASH_ADD_INT(users, id, s); /* id: name of key field */
}
查找
添加函數(shù)一樣,不同的key數(shù)據(jù)類型淮腾,有不同的添加函數(shù)糟需。如HASH_FIND_INT
查詢int類型的鍵值。
struct my_struct *find_user(int user_id) {
struct my_struct *s;
HASH_FIND_INT( users, &user_id, s);
return s;
}
這里第一個參數(shù)是對應(yīng)需要查詢的哈希表谷朝,第二個參數(shù)指向查詢的key的地址洲押,最后一個s 用于存儲查詢結(jié)果(結(jié)構(gòu)體指針s提前創(chuàng)建了,如果沒有查詢到 s 返回是 NULL圆凰,否則就返回對應(yīng)的key和val)杈帐。
對于鍵值,我們在添加的時候需要保證鍵值不重復(fù)添加专钉。所以對上面的版本進行修改挑童。如果 沒有找到對應(yīng)的鍵值,創(chuàng)建完整的key和val跃须。如果找到了站叼,add變成覆蓋原來的val。
void add_user(int user_id, char *name) {
struct my_struct *s;
HASH_FIND_INT(users, &user_id, s);
if (s == NULL) {
/* 如果 s 為 NULL菇民,需要重新 malloc
struct my_struct *s;
s = malloc(sizeof(struct my_struct));
*/
s = (struct my_struct *)malloc(sizeof *s);
s->id = user_id;
HASH_ADD_INT(users, id, s);
}
strcpy(s->name, name);
}
在上面的例子里面 users 作為一個全局變量尽楔,如果要設(shè)計成函數(shù)傳參傳遞數(shù)值呢。
直接把 這個 hash表的 指針加在 add_user
函數(shù)里面是不對的玉雾。
錯誤寫法
void add_user(struct my_struct *users, int user_id, char *names) {
...
HASH_FIND_INT(users, id, s);
}
需要傳入的是指向這個 hash表指針的指針, 可以參考上面的雙重指針的說明翔试。因為我們在HASH_ADD函數(shù)里,需要的是hash表指針本身的地址复旬,而不是這個指針指向的具體內(nèi)容垦缅。
正確寫法
void add_user(struct my_struct **users, int user_id, char *name) {
...
HASH_ADD_INT(*users, id, s);
}
刪除
刪除一個的元素的示例代碼:
void delete_user(struct my_struct *user) {
HASH_DEL(users, user); // user: pointer to delete
free(user); // optional,up to you
}
HASH_DEL
只會把一個結(jié)構(gòu)體實例從哈希表里面刪除驹碍,但是不會 free 掉
如果希望循環(huán)刪除掉所有的item并且free掉每個對象的空間壁涎。
void delete_all() {
struct my_struct *current_user, *tmp;
HASH_ITER(hh, users, current_user, tmp) {
HASH_DEL(users,current_user); /* delete; users advances to next */
free(current_user); /* optional- if you want to free */
}
}
如果只是想把哈希表結(jié)構(gòu)刪除凡恍,但是不用free掉每個元素。
HASH_CLEAR(hh, users);
之后怔球,users 這個指針被設(shè)置為NULL嚼酝。
計數(shù)
HASH_COUNT
用于計算hash表中item的總數(shù)目
unsigned int num_users;
num_users = HASH_COUNT(users);
printf("there are %u users\n", num_users);
遍歷
A hash is also a doubly-linked list.
Iterating backward and forward through the items in the hash is possible because of the hh.prev
and hh.next
fields. All the items in the hash can be reached by repeatedly following these pointers, thus the hash is also a doubly-linked list.
可以使用 hh.next
指針,指向下一個竟坛。
void print_users() {
struct my_struct *s;
for(s=users; s != NULL; s=s->hh.next) {
printf("user id %d: name %s\n", s->id, s->name);
}
}
還有就是上面刪除部分用過的HASH_ITER
排序
對于排序來講可以根據(jù)key或者根據(jù)val闽巩,或者結(jié)合起來
HASH_SORT(users, name_sort);
第二個參數(shù)是一個比較函數(shù),返回值是需要為int担汤。
int sort_function(void *a, void *b) {
/* compare a to b (cast a and b appropriately)
* return (int) -1 if (a < b)
* return (int) 0 if (a == b)
* return (int) 1 if (a > b)
*/
}
使用鍵值或者數(shù)值進行比較的示例涎跨。
int name_sort(struct my_struct *a, struct my_struct *b) {
return strcmp(a->name,b->name);
}
int id_sort(struct my_struct *a, struct my_struct *b) {
return (a->id - b->id);
}
void sort_by_name() {
HASH_SORT(users, name_sort);
}
void sort_by_id() {
HASH_SORT(users, id_sort);
}
When the items in the hash are sorted, the first item may change position. In the example above, users may point to a different structure after calling HASH_SORT.
實例1(LeetCode387)
給定一個字符串,找到它的第一個不重復(fù)的字符崭歧,并返回它的索引隅很。如果不存在,則返回 -1率碾。
示例:
s = "leetcode"
返回 0
s = "loveleetcode"
返回 2
代碼
struct hashTable {
int key;
int val;
UT_hash_handle hh;
};
void char_add(struct hashTable **hashHead, int key, int pos) {
struct hashTable *tmp;
HASH_FIND_INT(*hashHead, &key, tmp);
if (tmp != NULL) {
tmp->val = -1;
} else {
tmp = (struct hashTable*)malloc(sizeof *tmp);
tmp->key = key;
tmp->val = pos;
HASH_ADD_INT(*hashHead, key, tmp);
}
}
int find_no_duplicate(struct hashTable **hashHead, int length) {
struct hashTable *tmp;
int ans = length + 1;
for (tmp = *hashHead; tmp != NULL; tmp = tmp->hh.next) {
if (tmp->val != -1) {
ans = fmin(tmp->val, ans);
}
}
return (ans == length + 1)? -1 : ans;
}
void print(struct hashTable **hashHead) {
struct hashTable *s;
for(s=*hashHead; s != NULL; s=s->hh.next) {
printf("key %d: val %d\n", s->key, s->val);
}
}
int firstUniqChar(char * s){
struct hashTable *hashHead = NULL;
int length = strlen(s);
if (length == 0) return -1;
for(int i = 0; i < length; i++) {
char_add(&hashHead, s[i] , i);
}
//print(&hashHead);
return find_no_duplicate(&hashHead, length);
}
實例2 (LeetCode 49)
給定一個字符串?dāng)?shù)組叔营,將字母異位詞組合在一起。字母異位詞指字母相同所宰,但排列不同的字符串绒尊。
示例:
輸入: ["eat", "tea", "tan", "ate", "nat", "bat"]
輸出:
[
["ate","eat","tea"],
["nat","tan"],
["bat"]
]
/**
* Return an array of arrays of size *returnSize.
* The sizes of the arrays are returned as *returnColumnSizes array.
* Note: Both returned array and *columnSizes array must be malloced, assume caller calls free().
*/
#define STR_SIZE 100
static int compare(const void* x, const void* y){
return *(char*)x - *(char*)y;
}
typedef struct {
char key[STR_SIZE];
int value[STR_SIZE]; // value 是用來存儲每個同樣的詞的下標(biāo)的,方便結(jié)果輸出
int cnt;
UT_hash_handle hh;
} HashNode;
HashNode *head = NULL;
void add_item(char* s, int i){
HashNode *tmp;
HASH_FIND_STR(head, s, tmp);
if (tmp == NULL) {
tmp = (HashNode*)malloc(sizeof(HashNode));
strcpy(tmp->key, s);
tmp->cnt = 1;
tmp->value[0] = i;
HASH_ADD_STR(head, key, tmp);
} else {
tmp->value[tmp->cnt] = i;
(tmp->cnt) += 1;
}
return;
}
char *** groupAnagrams(char ** strs, int strsSize, int* returnSize, int** returnColumnSizes){
// 空輸入判斷
if (strs == NULL || strsSize < 1) {
*returnColumnSizes = (int*)malloc(sizeof(int) * 1);
(*returnColumnSizes)[0] = 0;
*returnSize = 0;
return NULL;
}
// 出參初始化
head = NULL;
*returnSize = 0; // 就是個標(biāo)量歧匈,用指針修飾是為了回傳
*returnColumnSizes = (int *)malloc(sizeof(int) * strsSize); // 一維 array
for (int i = 0; i < strsSize; i++) {
char curStr[STR_SIZE] = {0};
strcpy(curStr, strs[i]);
int len = strlen(curStr);
qsort(curStr, len, sizeof(char), compare);
add_item(curStr, i);
}
char*** result;
result = (char***)malloc(sizeof(char**) * HASH_COUNT(head));
*returnSize = HASH_COUNT(head);
*returnColumnSizes = (int*)malloc(sizeof(int) * (*returnSize));
// 遍歷
HashNode *iter; int index = 0;
for (iter = head; iter != NULL; iter = iter->hh.next) {
result[index] = (char**)malloc(sizeof(char*) * (iter->cnt));
(*returnColumnSizes)[index] = iter->cnt;
for (int i = 0; i < iter->cnt; i++) {
result[index][i] = (char*)malloc(sizeof(char) * (strlen(iter->key) + 1));
strcpy(result[index][i], strs[iter->value[i]]);
}
index++;
}
return result;
}