熬夜整理的C/C++萬(wàn)字總結(jié)(三)
大家好,我是唐唐!
1、位運(yùn)算
可以使用 C 對(duì)變量中的個(gè)別位進(jìn)行操作。您可能對(duì)人們想這樣做的原因感到奇怪。這種能力有時(shí)確實(shí)是必須的,或者至少是有用的。C 提供位的邏輯運(yùn)算符和移位運(yùn)算符。在以下例子中,我們將使用二進(jìn)制計(jì)數(shù)法寫出值,以便您可以了解對(duì)位發(fā)生的操作。在一個(gè)實(shí)際程序中,您可以使用一般的形式的整數(shù)變量或常量。例如不適用 00011001 的形式,而寫為 25 或者 031 或者 0x19.在我們的例子中,我們將使用8位數(shù)字,從左到右,每位的編號(hào)是 7 到 0。
1.1 位邏輯運(yùn)算符
4 個(gè)位運(yùn)算符用于整型數(shù)據(jù),包括 char。將這些位運(yùn)算符成為位運(yùn)算的原因是它們對(duì)每位進(jìn)行操作,而不影響左右兩側(cè)的位。請(qǐng)不要將這些運(yùn)算符與常規(guī)的邏輯運(yùn)算符(&& 、||和!)相混淆,常規(guī)的位的邏輯運(yùn)算符對(duì)整個(gè)值進(jìn)行操作。
1.1.1 按位取反~
一元運(yùn)算符~將每個(gè) 1 變?yōu)?0,將每個(gè) 0 變?yōu)?1,如下面的例子:
~(10011010)
01100101
假設(shè) a 是一個(gè)unsigned char,已賦值為 2。在二進(jìn)制中,2 是00000010.于是 -a 的值為11111101或者 253。請(qǐng)注意該運(yùn)算符不會(huì)改變 a 的值,a 仍為 2。
unsigned char a = 2; //00000010
unsigned char b = ~a; //11111101
printf("ret = %d\n", a); //ret = 2
printf("ret = %d\n", b); //ret = 253
1.1.2 位與(AND): &
二進(jìn)制運(yùn)算符 & 通過(guò)對(duì)兩個(gè)操作數(shù)逐位進(jìn)行比較產(chǎn)生一個(gè)新值。對(duì)于每個(gè)位,只有兩個(gè)操作數(shù)的對(duì)應(yīng)位都是 1 時(shí)結(jié)果才 為 1。
(10010011) & (00111101) = (00010001)
C 也有一個(gè)組合的位與-賦值運(yùn)算符:&=。下面兩個(gè)將產(chǎn)生相同的結(jié)果:
val &= 0377
val = val & 0377
1.1.3 位或(OR): |
二進(jìn)制運(yùn)算符 | 通過(guò)對(duì)兩個(gè)操作數(shù)逐位進(jìn)行比較產(chǎn)生一個(gè)新值。對(duì)于每個(gè)位,如果其中任意操作數(shù)中對(duì)應(yīng)的位為 1,那么結(jié)果位就為 1。
(10010011)| (00111101) = (10111111)
C 也有組合位或-賦值運(yùn)算符:|=
val |= 0377
val = val | 0377
**1.1.4 位異或: **
二進(jìn)制運(yùn)算符^對(duì)兩個(gè)操作數(shù)逐位進(jìn)行比較。對(duì)于每個(gè)位,如果操作數(shù)中的對(duì)應(yīng)位有一個(gè)是 1(但不是都是1),那么結(jié)果是 1.如果都是 0 或者都是 1,則結(jié)果位 0。
(10010011)^ (00111101) = (10101110)
C 也有一個(gè)組合的位異或 - 賦值運(yùn)算符:^=
val ^= 0377
val = val ^ 0377
1.1.5 用法
1.1.5.1 打開(kāi)位
已知:10011010:
1.將位 2 打開(kāi)
flag | 10011010
(10011010)|(00000100)=(10011110)
2.將所有位打開(kāi)
flag | ~flag
(10011010)|(01100101)=(11111111)
1.1.5.2 關(guān)閉位
flag & ~flag
(10011010)&(01100101)=(00000000)
1.1.5.3 轉(zhuǎn)置位
轉(zhuǎn)置(toggling)一個(gè)位表示如果該位打開(kāi),則關(guān)閉該位;如果該位關(guān)閉,則打開(kāi)。您可以使用位異或運(yùn)算符來(lái)轉(zhuǎn)置。其思想是如果 b 是一個(gè)位(1或0),那么如果 b 為 1 則 b^1 為 0,如果 b 為 0,則 1^b 為 1。無(wú)論 b 的值是 0 還是 1,0^b 為 b。
flag ^ 0xff
(10010011)^(11111111)=(01101100)
1.1.5.4 交換兩個(gè)數(shù)不需要臨時(shí)變量
//a ^ b = temp;
//a ^ temp = b;
//b ^ temp = a
(10010011)^(00100110)=(10110101)
(10110101)^(00100110)= 10010011
int a = 10;
int b = 30;
1.2 移位運(yùn)算符
現(xiàn)在讓我們了解一下 C 的移位運(yùn)算符。移位運(yùn)算符將位向左或向右移動(dòng)。同樣,我們?nèi)詫⒚鞔_地使用二進(jìn)制形式來(lái)說(shuō)明該機(jī)制的工作原理。
1.2.1 左移 <<
左移運(yùn)算符<<將其左側(cè)操作數(shù)的值的每位向左移動(dòng),移動(dòng)的位數(shù)由其右側(cè)操作數(shù)指定??粘鰜?lái)的位用 0 填充,并且丟棄移出左側(cè)操作數(shù)末端的位。在下面例子中,每位向左移動(dòng)兩個(gè)位置。
(10001010) << 2 = (00101000)
該操作將產(chǎn)生一個(gè)新位置,但是不改變其操作數(shù)。
1 << 1 = 2;
2 << 1 = 4;
4 << 1 = 8;
8 << 2 = 32
左移一位相當(dāng)于原值 *2。
1.2.2 右移 >>
右移運(yùn)算符>>將其左側(cè)的操作數(shù)的值每位向右移動(dòng),移動(dòng)的位數(shù)由其右側(cè)的操作數(shù)指定。丟棄移出左側(cè)操作數(shù)有段的位。對(duì)于unsigned類型,使用 0 填充左端空出的位。對(duì)于有符號(hào)類型,結(jié)果依賴于機(jī)器??粘龅奈豢赡苡?0 填充,或者使用符號(hào)(最左端)位的副本填充。
//有符號(hào)值
(10001010) >> 2
(00100010) //在某些系統(tǒng)上的結(jié)果值
(10001010) >> 2
(11100010) //在另一些系統(tǒng)上的結(jié)果
//無(wú)符號(hào)值
(10001010) >> 2
(00100010) //所有系統(tǒng)上的結(jié)果值
1.2.3 用法:移位運(yùn)算符
移位運(yùn)算符能夠提供快捷、高效(依賴于硬件)對(duì) 2 的冪的乘法和除法。
number << n: number乘以2的n次冪
number >> n:如果number非負(fù),則用number除以2的n次冪
2、數(shù)組
2.1 一維數(shù)組
元素類型角度:數(shù)組是相同類型的變量的有序集合 內(nèi)存角度:連續(xù)的一大片內(nèi)存空間

在討論多維數(shù)組之前,我們還需要學(xué)習(xí)很多關(guān)于一維數(shù)組的知識(shí)。首先讓我們學(xué)習(xí)一個(gè)概念。
2.1.1 數(shù)組名
考慮下面這些聲明:
int a;
int b[10];
我們把 a 稱作標(biāo)量,因?yàn)樗莻€(gè)單一的值,這個(gè)變量是的類型是一個(gè)整數(shù)。我們把 b 稱作數(shù)組,因?yàn)樗且恍┲档募稀O聵?biāo)和數(shù)名一起使用,用于標(biāo)識(shí)該集合中某個(gè)特定的值。例如,b[0] 表示數(shù)組 b 的第 1 個(gè)值,b[4] 表示第 5 個(gè)值。每個(gè)值都是一個(gè)特定的標(biāo)量。
那么問(wèn)題是 b 的類型是什么?它所表示的又是什么?一個(gè)合乎邏輯的答案是它表示整個(gè)數(shù)組,但事實(shí)并非如此。在 C中,在幾乎所有數(shù)組名的表達(dá)式中,數(shù)組名的值是一個(gè)指針常量,也就是數(shù)組第一個(gè)元素的地址。它的類型取決于數(shù)組元素的類型:如果他們是int類型,那么數(shù)組名的類型就是“指向 int 的常量指針”;如果它們是其他類型,那么數(shù)組名的類型也就是“指向其他類型的常量指針”。
請(qǐng)問(wèn):指針和數(shù)組是等價(jià)的嗎?
答案是否定的。數(shù)組名在表達(dá)式中使用的時(shí)候,編譯器才會(huì)產(chǎn)生一個(gè)指針常量。那么數(shù)組在什么情況下不能作為指針常量呢?在以下兩種場(chǎng)景下:
當(dāng)數(shù)組名作為sizeof操作符的操作數(shù)的時(shí)候,此時(shí)sizeof返回的是整個(gè)數(shù)組的長(zhǎng)度,而不是指針數(shù)組指針的長(zhǎng)度。 當(dāng)數(shù)組名作為&操作符的操作數(shù)的時(shí)候,此時(shí)返回的是一個(gè)指向數(shù)組的指針,而不是指向某個(gè)數(shù)組元素的指針常量。
int arr[10];
//arr = NULL; //arr作為指針常量,不可修改
int *p = arr; //此時(shí)arr作為指針常量來(lái)使用
printf("sizeof(arr):%d\n", sizeof(arr)); //此時(shí)sizeof結(jié)果為整個(gè)數(shù)組的長(zhǎng)度
printf("&arr type is %s\n", typeid(&arr).name()); //int(*)[10]而不是int*
2.1.2 下標(biāo)引用
int arr[] = { 1, 2, 3, 4, 5, 6 };
*(arr + 3),這個(gè)表達(dá)式是什么意思呢?
首先,我們說(shuō)數(shù)組在表達(dá)式中是一個(gè)指向整型的指針,所以此表達(dá)式表示 arr 指針向后移動(dòng)了 3 個(gè)元素的長(zhǎng)度。然后通過(guò)間接訪問(wèn)操作符從這個(gè)新地址開(kāi)始獲取這個(gè)位置的值。這個(gè)和下標(biāo)的引用的執(zhí)行過(guò)程完全相同。所以如下表達(dá)式是等同的:
*(arr + 3)
arr[3]
問(wèn)題1:數(shù)組下標(biāo)可否為負(fù)值?
問(wèn)題2:請(qǐng)閱讀如下代碼,說(shuō)出結(jié)果:
int arr[] = { 5, 3, 6, 8, 2, 9 };
int *p = arr + 2;
printf("*p = %d\n", *p);
printf("*p = %d\n", p[-1]);
那么是用下標(biāo)還是指針來(lái)操作數(shù)組呢?對(duì)于大部分人而言,下標(biāo)的可讀性會(huì)強(qiáng)一些。
2.1.3 數(shù)組和指針
指針和數(shù)組并不是相等的。為了說(shuō)明這個(gè)概念,請(qǐng)考慮下面兩個(gè)聲明:
int a[10];
int *b;
聲明一個(gè)數(shù)組時(shí),編譯器根據(jù)聲明所指定的元素?cái)?shù)量為數(shù)組分配內(nèi)存空間,然后再創(chuàng)建數(shù)組名,指向這段空間的起始位置。聲明一個(gè)指針變量的時(shí)候,編譯器只為指針本身分配內(nèi)存空間,并不為任何整型值分配內(nèi)存空間,指針并未初始化指向任何現(xiàn)有的內(nèi)存空間。
因此,表達(dá)式 *a 是完全合法的,但是表達(dá)式 *b 卻是非法的。*b 將訪問(wèn)內(nèi)存中一個(gè)不確定的位置,將會(huì)導(dǎo)致程序終止。另一方面b++可以通過(guò)編譯,a++ 卻不行,因?yàn)閍是一個(gè)常量值。
2.1.4 作為函數(shù)參數(shù)的數(shù)組名
當(dāng)一個(gè)數(shù)組名作為一個(gè)參數(shù)傳遞給一個(gè)函數(shù)的時(shí)候發(fā)生什么情況呢?
我們現(xiàn)在知道數(shù)組名其實(shí)就是一個(gè)指向數(shù)組第 1 個(gè)元素的指針,所以很明白此時(shí)傳遞給函數(shù)的是一份指針的拷貝。所以函數(shù)的形參實(shí)際上是一個(gè)指針。但是為了使程序員新手容易上手一些,編譯器也接受數(shù)組形式的函數(shù)形參。因此下面兩種函數(shù)原型是相等的:
int print_array(int *arr);
int print_array(int arr[]);
我們可以使用任何一種聲明,但哪一個(gè)更準(zhǔn)確一些呢?答案是指針。因?yàn)閷?shí)參實(shí)際上是個(gè)指針,而不是數(shù)組。同樣 sizeof arr 值是指針的長(zhǎng)度,而不是數(shù)組的長(zhǎng)度。
現(xiàn)在我們清楚了,為什么一維數(shù)組中無(wú)須寫明它的元素?cái)?shù)目了,因?yàn)樾螀⒅皇且粋€(gè)指針,并不需要為數(shù)組參數(shù)分配內(nèi)存。另一方面,這種方式使得函數(shù)無(wú)法知道數(shù)組的長(zhǎng)度。如果函數(shù)需要知道數(shù)組的長(zhǎng)度,它必須顯式傳遞一個(gè)長(zhǎng)度參數(shù)給函數(shù)。
2.2 多維數(shù)組
如果某個(gè)數(shù)組的維數(shù)不止1個(gè),它就被稱為多維數(shù)組。接下來(lái)的案例講解以二維數(shù)組舉例。
void test01(){
//二維數(shù)組初始化
int arr1[3][3] = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
int arr2[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int arr3[][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//打印二維數(shù)組
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j ++){
printf("%d ",arr1[i][j]);
}
printf("\n");
}
}
2.2.1 數(shù)組名
一維數(shù)組名的值是一個(gè)指針常量,它的類型是“指向元素類型的指針”,它指向數(shù)組的第 1 個(gè)元素。多維數(shù)組也是同理,多維數(shù)組的數(shù)組名也是指向第一個(gè)元素,只不過(guò)第一個(gè)元素是一個(gè)數(shù)組。例如:
int arr[3][10]
可以理解為這是一個(gè)一維數(shù)組,包含了 3 個(gè)元素,只是每個(gè)元素恰好是包含了 10 個(gè)元素的數(shù)組。arr 就表示指向它的第1個(gè)元素的指針,所以 arr 是一個(gè)指向了包含了 10 個(gè)整型元素的數(shù)組的指針。
2.2.2 指向數(shù)組的指針(數(shù)組指針)
數(shù)組指針,它是指針,指向數(shù)組的指針。
數(shù)組的類型由元素類型和數(shù)組大小共同決定:int array[5] 的類型為 int[5];
C 語(yǔ)言可通過(guò) typedef 定義一個(gè)數(shù)組類型:
定義數(shù)組指針有一下三種方式:
//方式一
void test01(){
//先定義數(shù)組類型,再用數(shù)組類型定義數(shù)組指針
int arr[10] = {1,2,3,4,5,6,7,8,9,10};
//有typedef是定義類型,沒(méi)有則是定義變量,下面代碼定義了一個(gè)數(shù)組類型ArrayType
typedef int(ArrayType)[10];
//int ArrayType[10]; //定義一個(gè)數(shù)組,數(shù)組名為ArrayType
ArrayType myarr; //等價(jià)于 int myarr[10];
ArrayType* pArr = &arr; //定義了一個(gè)數(shù)組指針pArr,并且指針指向數(shù)組arr
for (int i = 0; i < 10;i++){
printf("%d ",(*pArr)[i]);
}
printf("\n");
}
//方式二
void test02(){
int arr[10];
//定義數(shù)組指針類型
typedef int(*ArrayType)[10];
ArrayType pArr = &arr; //定義了一個(gè)數(shù)組指針pArr,并且指針指向數(shù)組arr
for (int i = 0; i < 10; i++){
(*pArr)[i] = i + 1;
}
for (int i = 0; i < 10; i++){
printf("%d ", (*pArr)[i]);
}
printf("\n");
}
//方式三
void test03(){
int arr[10];
int(*pArr)[10] = &arr;
for (int i = 0; i < 10; i++){
(*pArr)[i] = i + 1;
}
for (int i = 0; i < 10; i++){
printf("%d ", (*pArr)[i]);
}
printf("\n");
}
2.2.3 指針數(shù)組(元素為指針)
2.2.3.1 棧區(qū)指針數(shù)組
//數(shù)組做函數(shù)函數(shù),退化為指針
void array_sort(char** arr,int len){
for (int i = 0; i < len; i++){
for (int j = len - 1; j > i; j --){
//比較兩個(gè)字符串
if (strcmp(arr[j-1],arr[j]) > 0){
char* temp = arr[j - 1];
arr[j - 1] = arr[j];
arr[j] = temp;
}
}
}
}
//打印數(shù)組
void array_print(char** arr,int len){
for (int i = 0; i < len;i++){
printf("%s\n",arr[i]);
}
printf("----------------------\n");
}
void test(){
//主調(diào)函數(shù)分配內(nèi)存
//指針數(shù)組
char* p[] = { "bbb", "aaa", "ccc", "eee", "ddd"};
//char** p = { "aaa", "bbb", "ccc", "ddd", "eee" }; //錯(cuò)誤
int len = sizeof(p) / sizeof(char*);
//打印數(shù)組
array_print(p, len);
//對(duì)字符串進(jìn)行排序
array_sort(p, len);
//打印數(shù)組
array_print(p, len);
}
2.2.3.2 堆區(qū)指針數(shù)組
//分配內(nèi)存
char** allocate_memory(int n){
if (n < 0 ){
return NULL;
}
char** temp = (char**)malloc(sizeof(char*) * n);
if (temp == NULL){
return NULL;
}
//分別給每一個(gè)指針malloc分配內(nèi)存
for (int i = 0; i < n; i ++){
temp[i] = malloc(sizeof(char)* 30);
sprintf(temp[i], "%2d_hello world!", i + 1);
}
return temp;
}
//打印數(shù)組
void array_print(char** arr,int len){
for (int i = 0; i < len;i++){
printf("%s\n",arr[i]);
}
printf("----------------------\n");
}
//釋放內(nèi)存
void free_memory(char** buf,int len){
if (buf == NULL){
return;
}
for (int i = 0; i < len; i ++){
free(buf[i]);
buf[i] = NULL;
}
free(buf);
}
void test(){
int n = 10;
char** p = allocate_memory(n);
//打印數(shù)組
array_print(p, n);
//釋放內(nèi)存
free_memory(p, n);
}
2.2.4二維數(shù)組三種參數(shù)形式
2.2.4.1 二維數(shù)組的線性存儲(chǔ)特性
void PrintArray(int* arr, int len){
for (int i = 0; i < len; i++){
printf("%d ", arr[i]);
}
printf("\n");
}
//二維數(shù)組的線性存儲(chǔ)
void test(){
int arr[][3] = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
int arr2[][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int len = sizeof(arr2) / sizeof(int);
//如何證明二維數(shù)組是線性的?
//通過(guò)將數(shù)組首地址指針轉(zhuǎn)成Int*類型,那么步長(zhǎng)就變成了4,就可以遍歷整個(gè)數(shù)組
int* p = (int*)arr;
for (int i = 0; i < len; i++){
printf("%d ", p[i]);
}
printf("\n");
PrintArray((int*)arr, len);
PrintArray((int*)arr2, len);
}
2.2.4.2 二維數(shù)組的3種形式參數(shù)
//二維數(shù)組的第一種形式
void PrintArray01(int arr[3][3]){
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
printf("arr[%d][%d]:%d\n", i, j, arr[i][j]);
}
}
}
//二維數(shù)組的第二種形式
void PrintArray02(int arr[][3]){
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
printf("arr[%d][%d]:%d\n", i, j, arr[i][j]);
}
}
}
//二維數(shù)組的第二種形式
void PrintArray03(int(*arr)[3]){
for (int i = 0; i < 3; i++){
for (int j = 0; j < 3; j++){
printf("arr[%d][%d]:%d\n", i, j, arr[i][j]);
}
}
}
void test(){
int arr[][3] = {
{ 1, 2, 3 },
{ 4, 5, 6 },
{ 7, 8, 9 }
};
PrintArray01(arr);
PrintArray02(arr);
PrintArray03(arr);
}
2.3總結(jié)
2.3.1 編程提示
源代碼的可讀性幾乎總是比程序的運(yùn)行時(shí)效率更為重要 只要有可能,函數(shù)的指針形參都應(yīng)該聲明為 const。 在多維數(shù)組的初始值列表中使用完整的多層花括號(hào)提高可讀性
2.3.2 內(nèi)容總結(jié)
在絕大多數(shù)表達(dá)式中,數(shù)組名的值是指向數(shù)組第 1 個(gè)元素的指針。這個(gè)規(guī)則只有兩個(gè)例外,sizeof 和對(duì)數(shù)組名&。
指針和數(shù)組并不相等。當(dāng)我們聲明一個(gè)數(shù)組的時(shí)候,同時(shí)也分配了內(nèi)存。但是聲明指針的時(shí)候,只分配容納指針本身的空間。
當(dāng)數(shù)組名作為函數(shù)參數(shù)時(shí),實(shí)際傳遞給函數(shù)的是一個(gè)指向數(shù)組第 1 個(gè)元素的指針。
我們不單可以創(chuàng)建指向普通變量的指針,也可創(chuàng)建指向數(shù)組的指針。
