1. 程式人生 > 其它 >熬夜整理的c/c++萬字總結(三),值得收藏!

熬夜整理的c/c++萬字總結(三),值得收藏!

1、位運算

可以使用 C 對變數中的個別位進行操作。您可能對人們想這樣做的原因感到奇怪。這種能力有時確實是必須的,或者至少是有用的。C 提供位的邏輯運算子和移位運算子。在以下例子中,我們將使用二進位制計數法寫出值,以便您可以瞭解對位發生的操作。在一個實際程式中,您可以使用一般的形式的整數變數或常量。例如不適用00011001的形式,而寫為 25 或者 031 或者 0x19.在我們的例子中,我們將使用8位數字,從左到右,每位的編號是 7 到 0。

這一定是你需要的電子書資源,全!點選檢視!

程式設計師書籍資源,點選檢視!

1.1 位邏輯運算子

4 個位運算子用於整型資料,包括 char。將這些位運算子成為位運算的原因是它們對每位進行操作,而不影響左右兩側的位。請不要將這些運算子與常規的邏輯運算子(&& 、||和!)相混淆,常規的位的邏輯運算子對整個值進行操作。

1.1.1 按位取反~

一元運算子~將每個 1 變為 0,將每個 0 變為 1,如下面的例子:

~(10011010)
01100101

假設 a 是一個unsigned char,已賦值為 2。在二進位制中,2 是00000010.於是 -a 的值為11111101或者 253。請注意該運算子不會改變 a 的值,a 仍為 2。

unsignedchara=2;//00000010
unsignedcharb=~a;//11111101
printf("ret=%d\n",a);//ret=2
printf("ret=%d\n",b);//ret=253

1.1.2 位與(AND): &

二進位制運算子 & 通過對兩個運算元逐位進行比較產生一個新值。對於每個位,只有兩個運算元的對應位都是 1 時結果才 為 1。

(10010011) & (00111101) = (00010001)

C 也有一個組合的位與-賦值運算子:&=。下面兩個將產生相同的結果:

val&=0377
val=val&0377

1.1.3 位或(OR): |

二進位制運算子 | 通過對兩個運算元逐位進行比較產生一個新值。對於每個位,如果其中任意運算元中對應的位為 1,那麼結果位就為 1。

(10010011)| (00111101) = (10111111)

C 也有組合位或-賦值運算子: |=

val|=0377
val=val|0377

**1.1.4 位異或: **

二進位制運算子^對兩個運算元逐位進行比較。對於每個位,如果運算元中的對應位有一個是 1(但不是都是1),那麼結果是 1.如果都是 0 或者都是 1,則結果位 0。

(10010011)^ (00111101) = (10101110)

C 也有一個組合的位異或 - 賦值運算子: ^=

val^=0377
val=val^0377

1.1.5 用法

1.1.5.1 開啟位

已知:10011010:

1.將位 2 開啟

flag | 10011010

(10011010)|(00000100)=(10011110)

2.將所有位開啟

flag | ~flag

(10011010)|(01100101)=(11111111)

1.1.5.2 關閉位

flag & ~flag

(10011010)&(01100101)=(00000000)

1.1.5.3 轉置位

轉置(toggling)一個位表示如果該位開啟,則關閉該位;如果該位關閉,則開啟。您可以使用位異或運算子來轉置。其思想是如果 b 是一個位(1或0),那麼如果 b 為 1 則 b^1 為 0,如果 b 為 0,則 1^b 為 1。無論 b 的值是 0 還是 1,0^b 為 b。

flag ^ 0xff

(10010011)^(11111111)=(01101100)

1.1.5.4 交換兩個數不需要臨時變數

//a^b=temp;
//a^temp=b;
//b^temp=a
(10010011)^(00100110)=(10110101)
(10110101)^(00100110)=10010011

inta=10;
intb=30;

1.2 移位運算子

現在讓我們瞭解一下 C 的移位運算子。移位運算子將位向左或向右移動。同樣,我們仍將明確地使用二進位制形式來說明該機制的工作原理。

1.2.1 左移 <<

左移運算子<<將其左側運算元的值的每位向左移動,移動的位數由其右側運算元指定。空出來的位用 0 填充,並且丟棄移出左側運算元末端的位。在下面例子中,每位向左移動兩個位置。

(10001010) << 2 = (00101000)

該操作將產生一個新位置,但是不改變其運算元。

1<<1=2;
2<<1=4;
4<<1=8;
8<<2=32

左移一位相當於原值 *2。

1.2.2 右移 >>

右移運算子>>將其左側的運算元的值每位向右移動,移動的位數由其右側的運算元指定。丟棄移出左側運算元有段的位。對於unsigned型別,使用 0 填充左端空出的位。對於有符號型別,結果依賴於機器。空出的位可能用 0 填充,或者使用符號(最左端)位的副本填充。

//有符號值
(10001010)>>2
(00100010)//在某些系統上的結果值

(10001010)>>2
(11100010)//在另一些系統上的結果

//無符號值
(10001010)>>2
(00100010)//所有系統上的結果值

1.2.3 用法:移位運算子

移位運算子能夠提供快捷、高效(依賴於硬體)對 2 的冪的乘法和除法。

number << n: number乘以2的n次冪

number >> n: 如果number非負,則用number除以2的n次冪

2、陣列

2.1 一維陣列

  • 元素型別角度:陣列是相同型別的變數的有序集合

  • 記憶體角度:連續的一大片記憶體空間

在討論多維陣列之前,我們還需要學習很多關於一維陣列的知識。首先讓我們學習一個概念。

2.1.1 陣列名

考慮下面這些宣告:

inta;
intb[10];

我們把 a 稱作標量,因為它是個單一的值,這個變數是的型別是一個整數。我們把 b 稱作陣列,因為它是一些值的集合。下標和數名一起使用,用於標識該集合中某個特定的值。例如,b[0] 表示陣列 b 的第 1 個值,b[4] 表示第 5 個值。每個值都是一個特定的標量。

那麼問題是 b 的型別是什麼?它所表示的又是什麼?一個合乎邏輯的答案是它表示整個陣列,但事實並非如此。在 C中,在幾乎所有陣列名的表示式中,陣列名的值是一個指標常量,也就是陣列第一個元素的地址。它的型別取決於陣列元素的型別:如果他們是int型別,那麼陣列名的型別就是“指向 int 的常量指標”;如果它們是其他型別,那麼陣列名的型別也就是“指向其他型別的常量指標”。

請問:指標和陣列是等價的嗎?

答案是否定的。陣列名在表示式中使用的時候,編譯器才會產生一個指標常量。那麼陣列在什麼情況下不能作為指標常量呢?在以下兩種場景下:

  • 當陣列名作為sizeof操作符的運算元的時候,此時sizeof返回的是整個陣列的長度,而不是指標陣列指標的長度。

  • 當陣列名作為&操作符的運算元的時候,此時返回的是一個指向陣列的指標,而不是指向某個陣列元素的指標常量。

intarr[10];
//arr=NULL;//arr作為指標常量,不可修改
int*p=arr;//此時arr作為指標常量來使用
printf("sizeof(arr):%d\n",sizeof(arr));//此時sizeof結果為整個陣列的長度
printf("&arrtypeis%s\n",typeid(&arr).name());//int(*)[10]而不是int*

2.1.2 下標引用

intarr[]={1,2,3,4,5,6};

*(arr + 3),這個表示式是什麼意思呢?

首先,我們說陣列在表示式中是一個指向整型的指標,所以此表示式表示 arr 指標向後移動了 3 個元素的長度。然後通過間接訪問操作符從這個新地址開始獲取這個位置的值。這個和下標的引用的執行過程完全相同。所以如下表達式是等同的:

*(arr+3)
arr[3]

問題1:陣列下標可否為負值?

問題2:請閱讀如下程式碼,說出結果:

intarr[]={5,3,6,8,2,9};
int*p=arr+2;
printf("*p=%d\n",*p);
printf("*p=%d\n",p[-1]);

那麼是用下標還是指標來運算元組呢?對於大部分人而言,下標的可讀性會強一些。

2.1.3 陣列和指標

指標和陣列並不是相等的。為了說明這個概念,請考慮下面兩個宣告:

inta[10];
int*b;

宣告一個數組時,編譯器根據宣告所指定的元素數量為陣列分配記憶體空間,然後再建立陣列名,指向這段空間的起始位置。宣告一個指標變數的時候,編譯器只為指標本身分配記憶體空間,並不為任何整型值分配記憶體空間,指標並未初始化指向任何現有的記憶體空間。

因此,表示式 *a 是完全合法的,但是表示式 *b 卻是非法的。*b 將訪問記憶體中一個不確定的位置,將會導致程式終止。另一方面b++可以通過編譯,a++ 卻不行,因為a是一個常量值。

2.1.4 作為函式引數的陣列名

當一個數組名作為一個引數傳遞給一個函式的時候發生什麼情況呢?

我們現在知道陣列名其實就是一個指向陣列第 1 個元素的指標,所以很明白此時傳遞給函式的是一份指標的拷貝。所以函式的形參實際上是一個指標。但是為了使程式設計師新手容易上手一些,編譯器也接受陣列形式的函式形參。因此下面兩種函式原型是相等的:

intprint_array(int*arr);
intprint_array(intarr[]);

我們可以使用任何一種宣告,但哪一個更準確一些呢?答案是指標。因為實參實際上是個指標,而不是陣列。同樣 sizeof arr 值是指標的長度,而不是陣列的長度。

現在我們清楚了,為什麼一維陣列中無須寫明它的元素數目了,因為形參只是一個指標,並不需要為陣列引數分配記憶體。另一方面,這種方式使得函式無法知道陣列的長度。如果函式需要知道陣列的長度,它必須顯式傳遞一個長度引數給函式。

2.2 多維陣列

如果某個陣列的維數不止1個,它就被稱為多維陣列。接下來的案例講解以二維陣列舉例。

voidtest01(){
//二維陣列初始化
intarr1[3][3]={
{1,2,3},
{4,5,6},
{7,8,9}
};
intarr2[3][3]={1,2,3,4,5,6,7,8,9};
intarr3[][3]={1,2,3,4,5,6,7,8,9};

//列印二維陣列
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
printf("%d",arr1[i][j]);
}
printf("\n");
}
}

2.2.1 陣列名

一維陣列名的值是一個指標常量,它的型別是“指向元素型別的指標”,它指向陣列的第 1 個元素。多維陣列也是同理,多維陣列的陣列名也是指向第一個元素,只不過第一個元素是一個數組。例如:

intarr[3][10]

可以理解為這是一個一維陣列,包含了 3 個元素,只是每個元素恰好是包含了 10 個元素的陣列。arr 就表示指向它的第1個元素的指標,所以 arr 是一個指向了包含了 10 個整型元素的陣列的指標。

2.2.2 指向陣列的指標(陣列指標)

陣列指標,它是指標,指向陣列的指標。

陣列的型別由元素型別和陣列大小共同決定:int array[5]的型別為int[5]

C 語言可通過 typedef 定義一個數組型別:

定義陣列指標有一下三種方式:

//方式一
voidtest01(){

//先定義陣列型別,再用陣列型別定義陣列指標
intarr[10]={1,2,3,4,5,6,7,8,9,10};
//有typedef是定義型別,沒有則是定義變數,下面程式碼定義了一個數組型別ArrayType
typedefint(ArrayType)[10];
//intArrayType[10];//定義一個數組,陣列名為ArrayType

ArrayTypemyarr;//等價於intmyarr[10];
ArrayType*pArr=&arr;//定義了一個數組指標pArr,並且指標指向陣列arr
for(inti=0;i<10;i++){
printf("%d",(*pArr)[i]);
}
printf("\n");
}

//方式二
voidtest02(){

intarr[10];
//定義陣列指標型別
typedefint(*ArrayType)[10];
ArrayTypepArr=&arr;//定義了一個數組指標pArr,並且指標指向陣列arr
for(inti=0;i<10;i++){
(*pArr)[i]=i+1;
}
for(inti=0;i<10;i++){
printf("%d",(*pArr)[i]);
}
printf("\n");

}

//方式三
voidtest03(){

intarr[10];
int(*pArr)[10]=&arr;

for(inti=0;i<10;i++){
(*pArr)[i]=i+1;

}
for(inti=0;i<10;i++){
printf("%d",(*pArr)[i]);
}
printf("\n");
}

2.2.3 指標陣列(元素為指標)

2.2.3.1 棧區指標陣列

//陣列做函式函式,退化為指標
voidarray_sort(char**arr,intlen){

for(inti=0;i<len;i++){
for(intj=len-1;j>i;j--){
//比較兩個字串
if(strcmp(arr[j-1],arr[j])>0){
char*temp=arr[j-1];
arr[j-1]=arr[j];
arr[j]=temp;
}
}
}

}

//列印陣列
voidarray_print(char**arr,intlen){
for(inti=0;i<len;i++){
printf("%s\n",arr[i]);
}
printf("----------------------\n");
}

voidtest(){

//主調函式分配記憶體
//指標陣列
char*p[]={"bbb","aaa","ccc","eee","ddd"};
//char**p={"aaa","bbb","ccc","ddd","eee"};//錯誤
intlen=sizeof(p)/sizeof(char*);
//列印陣列
array_print(p,len);
//對字串進行排序
array_sort(p,len);
//列印陣列
array_print(p,len);
}

2.2.3.2 堆區指標陣列

//分配記憶體
char**allocate_memory(intn){

if(n<0){
returnNULL;
}

char**temp=(char**)malloc(sizeof(char*)*n);
if(temp==NULL){
returnNULL;
}

//分別給每一個指標malloc分配記憶體
for(inti=0;i<n;i++){
temp[i]=malloc(sizeof(char)*30);
sprintf(temp[i],"%2d_helloworld!",i+1);
}

returntemp;
}

//列印陣列
voidarray_print(char**arr,intlen){
for(inti=0;i<len;i++){
printf("%s\n",arr[i]);
}
printf("----------------------\n");
}

//釋放記憶體
voidfree_memory(char**buf,intlen){
if(buf==NULL){
return;
}
for(inti=0;i<len;i++){
free(buf[i]);
buf[i]=NULL;
}

free(buf);
}

voidtest(){

intn=10;
char**p=allocate_memory(n);
//列印陣列
array_print(p,n);
//釋放記憶體
free_memory(p,n);
}

2.2.4二維陣列三種引數形式

2.2.4.1 二維陣列的線性儲存特性

voidPrintArray(int*arr,intlen){
for(inti=0;i<len;i++){
printf("%d",arr[i]);
}
printf("\n");
}

//二維陣列的線性儲存
voidtest(){
intarr[][3]={
{1,2,3},
{4,5,6},
{7,8,9}
};

intarr2[][3]={1,2,3,4,5,6,7,8,9};
intlen=sizeof(arr2)/sizeof(int);

//如何證明二維陣列是線性的?
//通過將陣列首地址指標轉成Int*型別,那麼步長就變成了4,就可以遍歷整個陣列
int*p=(int*)arr;
for(inti=0;i<len;i++){
printf("%d",p[i]);
}
printf("\n");

PrintArray((int*)arr,len);
PrintArray((int*)arr2,len);
}

2.2.4.2 二維陣列的3種形式引數

//二維陣列的第一種形式
voidPrintArray01(intarr[3][3]){
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
printf("arr[%d][%d]:%d\n",i,j,arr[i][j]);
}
}
}

//二維陣列的第二種形式
voidPrintArray02(intarr[][3]){
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
printf("arr[%d][%d]:%d\n",i,j,arr[i][j]);
}
}
}

//二維陣列的第二種形式
voidPrintArray03(int(*arr)[3]){
for(inti=0;i<3;i++){
for(intj=0;j<3;j++){
printf("arr[%d][%d]:%d\n",i,j,arr[i][j]);
}
}
}

voidtest(){

intarr[][3]={
{1,2,3},
{4,5,6},
{7,8,9}
};

PrintArray01(arr);
PrintArray02(arr);
PrintArray03(arr);
}

2.3總結

2.3.1 程式設計提示

  • 原始碼的可讀性幾乎總是比程式的執行時效率更為重要

  • 只要有可能,函式的指標形參都應該宣告為 const。

  • 在多維陣列的初始值列表中使用完整的多層花括號提高可讀性

2.3.2 內容總結

在絕大多數表示式中,陣列名的值是指向陣列第 1 個元素的指標。這個規則只有兩個例外,sizeof 和對陣列名&。

指標和陣列並不相等。當我們宣告一個數組的時候,同時也分配了記憶體。但是宣告指標的時候,只分配容納指標本身的空間。

當陣列名作為函式引數時,實際傳遞給函式的是一個指向陣列第 1 個元素的指標。

我們不單可以建立指向普通變數的指標,也可建立指向陣列的指標。

app開發難度大嗎