1. 指針和數組
數組指針 和 指針數組
?int* p1[10]; // 指針數組int (*p2)[10]; // 數組指針
因為 [] 的優先級比 * 高,p先和 [] 結合說明p是一個數組,p先和*結合說明p是一個指針
括號保證p先和*結合,說明p是一個指針變量,然后指著指向的是一個大小為10個整型的數組。所以p是一個 指針,指向一個數組,叫數組指針。
arr和&arr的區別
arr代表數組首元素的地址,&arr代表整個數組的地址
?void test(int(*arr)[10], int size) // 這里arr也是整個數組的數組指針{for (int i = 0; i < size; ++i){cout << ((int*)arr)[i] << " ";}cout << endl;}?int main(){int arr[10] = { 0 };int(*p)[10] = &arr; // 數組指針需要指整個數組test(p, 10);return 0;}
二維數組傳參
?
void test(int arr[3][5])//ok?{}void test(int arr[][])//ok? X{}void test(int arr[][5])//ok?{}// 總結:二維數組傳參,函數形參的設計只能省略第一個[]的數字。// 因為對一個二維數組,可以不知道有多少行,但是必須知道一行多少元素。這樣才方便運算。void test(int* arr)//ok?X{}void test(int* arr[5])//ok?{}void test(int(*arr)[5])//ok?arr是指向一個大小為5的一維數組{}void test(int** arr)//ok?{}int main(){int arr[3][5] = { 0 };test(arr);}
函數指針
保存函數的地址:函數指針
?#include <stdio.h>void test(){}?int main(){printf("%p\n", test);printf("%p\n", &test); // 一樣cout << typeid(test).name() << endl; // void __cdecl(void) 函數名cout << typeid(&test).name() << endl; // void (__cdecl*)(void) 函數指針void(*p1)(void) = test;void(*p2)(void) = &test; // 一樣的return 0;}
函數指針數組
?typedef void(*handler)(void);?int main(){handler arr[12] = { 0 };void(*arr1[12])(void) ?= { 0 };}
const和指針
const修飾的指針變量:
-
const位于*前的,表示指針指向的對象內容無法修改,p指向的空間內容(指向對象的內容)無法修改
-
const位于*后面的,表示指針指向的位置無法修改,p的內容(保存的對象地址)無法修改
? const int* p = nullptr;int const* p = nullptr;int* const p = nullptr;
sizeof和指針,數組/strlen和指針,數組
sizeof是根據對象的類型判斷大小,但是有一個特殊處理就是數組名,sizeof(數組名)
-
sizeof(數組名),這里的數組名表示整個數組,計算的是整個數組的大小
-
&數組名,這里的數組名表示整個數組,取出的是整個數組的地址
-
除此之外所有的數組名都表示首元素的地址
-
但是參數數組也是一個特殊的存在,當數組作為參數進行傳遞的時候,數組其實退化成了指針
?//一維數組int a[] = {1,2,3,4};printf("%d\n",sizeof(a)); // 16printf("%d\n",sizeof(a+0)); // 4/8printf("%d\n",sizeof(*a)); // 4 printf("%d\n",sizeof(a+1)); // 4/8printf("%d\n",sizeof(a[1])); // 4printf("%d\n",sizeof(&a)); // 4/8printf("%d\n",sizeof(*&a)); // 16(*和&抵消了)printf("%d\n",sizeof(&a+1)); // 4/8printf("%d\n",sizeof(&a[0])); // 4/8printf("%d\n",sizeof(&a[0]+1)); // 4/8// 字符數組char arr[] = {'a','b','c','d','e','f'}; // 6個 系統不會給最后補0 ""這樣賦值才行printf("%d\n", sizeof(arr)); // 6printf("%d\n", sizeof(arr+0)); // 4/8printf("%d\n", sizeof(*arr)); // 1printf("%d\n", sizeof(arr[1])); // 1printf("%d\n", sizeof(&arr)); // 4/8printf("%d\n", sizeof(&arr+1)); // 4/8printf("%d\n", sizeof(&arr[0]+1)); // 4/8printf("%d\n", strlen(arr)); // 未知printf("%d\n", strlen(arr+0)); // 未知printf("%d\n", strlen(*arr)); // 錯誤printf("%d\n", strlen(arr[1])); // 錯誤printf("%d\n", strlen(&arr)); // 報錯printf("%d\n", strlen(&arr+1)); // 報錯,因為&arr的類型char(*)[6]printf("%d\n", strlen(&arr[0]+1)); // 未知 優先級 [] > * > &char arr[] = "abcdef"; // 7個 最后補0printf("%d\n", sizeof(arr)); // 7printf("%d\n", sizeof(arr+0)); // 4/8printf("%d\n", sizeof(*arr)); // 1printf("%d\n", sizeof(arr[1])); // 1printf("%d\n", sizeof(&arr)); // 4/8printf("%d\n", sizeof(&arr+1)); // 4/8printf("%d\n", sizeof(&arr[0]+1)); // 4/8printf("%d\n", strlen(arr)); // 6printf("%d\n", strlen(arr+0)); // 6printf("%d\n", strlen(*arr)); // 報錯printf("%d\n", strlen(arr[1])); // 報錯printf("%d\n", strlen(&arr)); // 報錯printf("%d\n", strlen(&arr+1)); // 報錯printf("%d\n", strlen(&arr[0]+1)); // 5const char *p = "abcdef"; // 最后會補'\0'printf("%d\n", sizeof(p)); // 4/8printf("%d\n", sizeof(p+1)); // 4/8printf("%d\n", sizeof(*p)); // 1printf("%d\n", sizeof(p[0])); // 1printf("%d\n", sizeof(&p)); // 4/8printf("%d\n", sizeof(&p+1)); // 4/8printf("%d\n", sizeof(&p[0]+1)); // 4/8printf("%d\n", strlen(p)); // 6printf("%d\n", strlen(p+1)); // 5printf("%d\n", strlen(*p)); // 報錯printf("%d\n", strlen(p[0])); // 報錯printf("%d\n", strlen(&p)); // 報錯printf("%d\n", strlen(&p+1)); // 報錯printf("%d\n", strlen(&p[0]+1)); // 5?//二維數組int a[3][4] = {0};printf("%d\n",sizeof(a)); // 48printf("%d\n",sizeof(a[0][0])); // 4printf("%d\n",sizeof(a[0])); // 16printf("%d\n",sizeof(a[0]+1)); // 4/8 (指針) a[0][1]// 這里a[0] 表示a的首個元素,因為sizeof的特殊所以被當成整個數組大小 +1 后這個特殊就沒了printf("%d\n",sizeof(*(a[0]+1))); // 4printf("%d\n",sizeof(a+1)); // 4/8printf("%d\n",sizeof(*(a+1))); // 4/8X 16 a[1]printf("%d\n",sizeof(&a[0]+1)); // 4/8 printf("%d\n",sizeof(*(&a[0]+1))); // 4X 16 ? a[1]printf("%d\n",sizeof(*a)); // 4/8X 16 a[0]printf("%d\n",sizeof(a[3])); // 4/8X 16
總結:先看類型再判斷
2. 庫函數的模擬實現
memcpy
?void* memcpy(void* dest, const void* src, size_t num){assert(dest && src);char* d = (char*)dest;const char* s = (const char*)src;while (num--){*d++ = *s++;}return dest;}
注意:c++使用括號強轉類型,生成的是臨時變量,不能進行++
memmove
?void* memmove(void* dest, const void* src, size_t num){assert(dest && src);char* d = static_cast<char*>(dest);const char* s = static_cast<const char*>(src);while (num--){if (dest < src){*d++ = *s++;}else{*((char*)(d + num)) = *(s + num); // 這里根據num的減少來推進}}return dest;}
strstr
?// 從目的字符串中找src字符串static char* strstr(const char* dest, const char* src){assert(dest && src);const char* left = dest, * right = dest;const char* cur = src;while (true){while (*left != '\0' && *left != *cur) left++;if (*left == '\0')break;// *left == *curright = left;while (*right == *cur){right++;cur++;if (*cur == '\0')return const_cast<char*>(left);}cur = src; // cur 回執left++;}return nullptr;}
memset/strcmp
?void* memset(void* ptr, int val, size_t num){assert(ptr);char* cur = static_cast<char*>(ptr);while (num--){*cur++ = val;}return ptr;}int strcmp(const char* str1, const char* str2){assert(str1 && str2);while (*str1 != '\0' && *str2 != '\0' && *str1++ == *str2++);//if (*str1 < *str2)// return -1;//else if (*str1 > *str2)// return 1;//else return 0;return *str1 - *str2;}
3. 自定義類型
內存對齊規則
-
第一個成員在與結構體變量偏移量為0的地址處。
-
其他成員變量要對齊到某個數字(對齊數)的整數倍的地址處。
-
結構體總大小為最大對齊數(每個成員變量都有一個對齊數)的整數倍。
-
如果嵌套了結構體的情況,嵌套的結構體對齊到自己的最大對齊數的整數倍處,結構體的整體大小就是所有最大對齊數(含嵌套結構體的對齊數)的整數倍。
對齊數 = 編譯器默認的一個對齊數(VS下是8) 與 該成員大小的較小值
聯合體
聯合也是一種特殊的自定義類型 這種類型定義的變量也包含一系列的成員,特征是這些成員公用同一塊空間(所以聯合也叫共用體)
聯合的成員是共用同一塊內存空間的,這樣一個聯合變量的大小,至少是最大成員的大小(因為 聯合至少得有能力保存最大的那個成員)
聯合大小的計算:
聯合的大小至少是最大成員的大小,當最大成員大小不是最大對齊數的整數倍的時候,就要對齊到最大對齊數的整數倍
4. 整形的存儲規則
原碼/反碼/補碼
計算機中的有符號數有三種表示方法,即原碼、反碼和補碼
三種表示方法均有符號位和數值位兩部分,符號位都是用0表示“正”,用1表示“負”,而數值位三種表示方法各不相同
-
原碼:直接將二進制按照正負數的形式翻譯成二進制就可以
-
反碼: 將原碼的符號位不變,其他位依次按位取反就可以得到了
-
補碼: 反碼+1就得到補碼
正數的原、反、補碼都相同
對于整形來說:數據存放內存中其實存放的是補碼
大小端
大端(存儲)模式,是指數據的低位保存在內存的高地址中,而數據的高位,保存在內存的低地址中(高低)
小端(存儲)模式,是指數據的低位保存在內存的低地址中,而數據的高位,保存在內存的高地址中(高高)
如何判斷:
?#include <stdio.h>int check_sys(){int i = 1;return (*(char *)&i);}int main(){int ret = check_sys();if(ret == 1)printf("小端\n");elserintf("大端\n");return 0;}//代碼2int check_sys(){union{int i;char c;}un;un.i = 1;return un.c;}
5. 編譯鏈接
宏
#define 替換規則 在程序中擴展#define定義符號和宏時,需要涉及幾個步驟:
-
在調用宏時,首先對參數進行檢查,看看是否包含任何由#define定義的符號。如果是,它們首先被替換。
-
替換文本隨后被插入到程序中原來文本的位置。對于宏,參數名被他們的值替換。
-
最后,再次對結果文件進行掃描,看看它是否包含任何由#define定義的符號。如果是,就重復上述處理過程。
注意:
-
宏參數和#define 定義中可以出現其他#define定義的變量。但是對于宏,不能出現遞歸
-
當預處理器搜索#define定義的符號的時候,字符串常量的內容并不被搜索。
也就是說"宏"只會被當成字符串,宏不會生效,這時#宏:把一個宏參數變成對應的字符串
?#include <stdio.h>?#define PRINT1(FORMAT, VALUE) \printf("the value is "FORMAT"\n", VALUE)?#define PRINT2(FORMAT, VALUE) \printf("the value of "#VALUE" is "FORMAT"\n", VALUE) // yes//printf("the value of ""VALUE"" is "FORMAT"\n", VALUE) // no?int main(){char a = -1;signed char b = -1;unsigned char c = -1;printf("a=%d,b=%d,c=%d\n", a, b, c); // 對齊 -1 -1 255printf("file:%s\n line:%d\n time:%s\n", __FILE__, __LINE__, __TIME__);const char* p = "hello ""bit\n"; // 字符串合并printf("%s\n", p);PRINT1("%d", 10);PRINT2("%d", 10);return 0;}
可以把位于它兩邊的符號合成一個符號。 它允許宏定義從分離的文本片段創建標識符
?#define OFFSETOF(struct_name, member_name) \((size_t)&(((struct_name*)0)->member_name))// 獲取成員變量的偏移量
宏的優缺點:
優點
-
用于調用函數和從函數返回的代碼可能比實際執行這個小型計算工作所需要的時間更多。所以宏比函數在程序 的規模和速度方面更勝一籌。
-
更為重要的是函數的參數必須聲明為特定的類型。所以函數只能在類型合適的表達式上使用。反之這個宏怎可 以適用于整形、長整型、浮點型等可以用于>來比較的類型。宏是類型無關的。(C++模板)
缺點
-
每次使用宏的時候,一份宏定義的代碼將插入到程序中。除非宏比較短,否則可能大幅度增加程序的長度。
-
宏是沒法調試的。
-
宏由于類型無關,也就不夠嚴謹。
-
宏可能會帶來運算符優先級的問題,導致程序容易出現錯。
#undef 于移除一個宏定義
?gcc -D ARRAY_SIZE=10 programe.c // 命令行宏定義
條件編譯
?#if defined(OS_UNIX) #ifdef OPTION1 unix_version_option1(); #elif defined(OPTION2)unix_version_option2(); #elseunix_version_option3(); #endif #elif defined(OS_MSDOS) #ifdef OPTION2 msdos_version_option2(); #endif #endif