深入理解指針(5)
- 一、sizeof和strlen的對比
- 1.1sizeof
- 1.2strlen
- 1.3sizeof和strlen的對比
- 二、數組和指針筆試題解析
- 2.1 一維數組
- 2.2 字符數組
- 2.2.1代碼1:
- 2.2.2代碼2:
- 2.2.3代碼3:
- 2.2.4代碼4:
- 2.2.5代碼5:
- 2.2.6代碼6:
- 2.3 二維數組
- 三、指針運算筆試題解析
- 3.1題目1:
- 3.2題目2:
- 3.3題目3:
- 3.4題目4:
- 3.5題目5:
- 3.6題目6:
- 3.7題目7:
- 四、總結
- 1. `sizeof`與`strlen`的區別
- 2. 數組和指針的關系
- 3. 字符數組與字符串
- 4. 指針運算
- 總結
一、sizeof和strlen的對比
1.1sizeof
sizeof是單目操作符(絕不是函數!!!),sizeof計算變量所占內存空間的大小的,單位是字節。
如果操作數是類型的話,計算的是使用類型創建的變量所占內存空間的大小。
注:sizeof只關注占用內存空間的大小,不在乎內存中存放什么數據。
比如:
#inculde <stdio.h>int main(){int a = 10;printf("%d\n", sizeof(a));printf("%d\n", sizeof a);printf("%d\n", sizeof(int));return 0;}
1.2strlen
strlen是C語言庫函數,功能是求字符串長度。
函數原型是:
size_t strlen ( const char * str );
統計的是從strlen函數的參數str中這個地址開始向后,\0之前字符串中字符的個數。
注:strlen 函數會一直向后找 \0 字符,直到找到為止,所以可能存在越界查找。
代碼如下:
#include <stdio.h>
int main()
{char arr1[3] = {'a', 'b', 'c'};char arr2[] = "abc";printf("%d\n", strlen(arr1));printf("%d\n", strlen(arr2));printf("%d\n", sizeof(arr1));printf("%d\n", sizeof(arr2));return 0;
}
運行結果如下:
1.3sizeof和strlen的對比
sizeof | strlen |
---|---|
1.sizeof是操作符 | 1.strlen是庫函數,使用需要包含頭文件string.h |
2.sizeof計算操作數所占內存的大小,單位是字節 | 2.strlen是求字符串長度的,統計的是\0之前字符的個數 |
3.不關注內存中存放什么數據 | 3.關注內存中是否有\0,如果沒有\0,就會持續往后找,可能會越界 |
4.sizeof括號中有表達式的話,表達式是不參與計算的!!! |
用代碼檢驗4,代碼如下:
#include <stdio.h>
int main()
{
int a = 8;
short s = 4;
printf("%d\n",sizeof(s = a + 2));//2
printf("%d\n",s);//4
return 0;
}
運行結果如下:
那么為什么sizeof中的表達式不計算?
C語言是編譯型語言,在編譯期這個表達式并不會被執行,sizeof 運算的結果是在編譯期間就已知的常數值,并不需要等到運行時才求解。因此,對于其中涉及到的操作數或者操作本身都不需要實際執行。
二、數組和指針筆試題解析
2.1 一維數組
*a == a[0] == *(a + 0)
數組名的理解:數組名是數組首元素(第一個元素)的地址。
但是有2個是例外:1.sizeof(數組名) —— 數組名表示整個數組,計算的是整個數組的大小,單位是字節。
2.&數組名 —— 數組名表示的是整個數組,取出的是整個數組的地址。
除此之外,所有的數組名是數組首元素(第一個元素)的地址。
筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
int a[] = {1,2,3,4};//數組有幾個元素?//4
printf("%zd\n",sizeof(a));//16
printf("%zd\n",sizeof(a + 0));//a是首元素的地址 —— 類型是int*,a + 0還是首元素的地址,是地址大小就是4/8。
printf("%zd\n",sizeof(*a));//a是首元素的地址,*a是首元素,大小就是4個字節。
printf("%zd\n",sizeof(a + 1));//a是首元素地址,類型是int*,a + 1跳過1個整型,a + 1就是第二個元素的地址,是地址大小就是4/8。
printf("%zd\n",sizeof(a[1]));//a[1]就是第二個元素,大小是4個字節。
printf("%zd\n",sizeof(&a));//&a是數組的地址,數組的地址也是地址,是地址大小就是4/8字節。
printf("%zd\n",sizeof(*&a));//1.*&互相抵消了,等價于sizeof(a),16
//2.&a是數組的地址,類型是int(*)[4],對數組指針解引用訪問的是數組,計算的是數組的大小,16
//char* —— 解引用訪問的是char
//int* —— 解引用訪問的是int
printf("%zd\n",sizeof(&a + 1));//&a + 1是跳過這個數組后的那個位置的地址,是地址大小就是4/8字節。
printf("%zd\n",sizeof(&a[0]));//首元素的地址,大小就是4/8字節。
printf("%zd\n",sizeof(&a[0] + 1));//&a[0] + 1 —— 數組第二個元素的地址,大小就是4/8字節。
return 0;
}
運行結果如下圖:
2.2 字符數組
2.2.1代碼1:
筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n",sizeof(arr));//數組名單獨放在sizeof內部,計算的是數組的大小,單位為字節,6。
printf("%d\n",sizeof(arr + 0));//arr是數組名表示首元素的地址,arr + 0還是首元素的地址,是地址就是4/8字節。
printf("%d\n",sizeof(*arr));//arr是首元素的地址,*arr就是首元素,大小就是1個字節。
//*arr == arr[0] == *(arr + 0)
printf("%d\n",sizeof(arr[1]));//arr[1]是第二個元素,大小也是1個字節。
printf("%d\n",sizeof(&arr));//&arr是數組的地址,數組的地址也是地址,是地址大小就是4/8個字節。
//&arr —— char(*)[6]
printf("%d\n",sizeof(&arr + 1));//4/8個字節,&arr + 1,跳過整個數組,指向了數組后邊的空間。
printf("%d\n",sizeof(&arr[0] + 1));//第二個元素的地址,是地址就是4/8個字節。
return 0;
}
運行結果如下圖:
2.2.2代碼2:
筆試題代碼和解析如下:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = {'a','b','c','d','e','f'};
printf("%d\n",strlen(arr));//arr是首元素的地址,數組中沒有\0,就會導致越界訪問,結果就是隨機的。
printf("%d\n",strlen(arr + 0));//arr + 0是數組首元素的地址,數組中沒有\0,就會導致越界訪問,結果就是隨機的。
printf("%d\n",strlen(*arr));//arr是首元素的地址,*arr是首元素,就是'a','a'的ASCII碼值是97,就相當于97作為地址傳遞給了strlen,strlen得到的就是野指針,代碼是有問題的。
printf("%d\n",strlen(arr[1]));//arr[1] —— 'b' —— 98,傳給strlen函數也是錯誤的。
printf("%d\n",strlen(&arr));//&arr是數組的地址,起始位置是數組的第一個元素的位置,隨機值。
printf("%d\n",strlen(&arr + 1));//隨機值。
printf("%d\n",strlen(&arr[0] + 1));//從第二個元素開始向后統計的,得到的也是隨機值。
return 0;
}
運行結果如下圖:
2.2.3代碼3:
筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n",sizeof(arr));//7,計算的是數組總大小,數組名單獨放在sizeof內部。
printf("%d\n",sizeof(arr + 0));//arr表示數組首元素的地址,arr + 0還是首元素的地址,4/8字節。
printf("%d\n",sizeof(*arr));//arr表示數組首元素的地址,*arr是首元素,大小是1字節。
printf("%d\n",sizeof(arr[1]));//arr[1]是第二個元素,大小是1個字節。
printf("%d\n",sizeof(&arr));//&arr是數組的地址,是地址就是4/8字節。
printf("%d\n",sizeof(&arr + 1));//&arr是數組的地址,是地址就是4/8字節。
printf("%d\n",sizeof(&arr[0] + 1));//&arr是數組的地址,+1跳過整個數組,還是地址,是地址就是4/8字節。
return 0;
}
運行結果如下圖:
2.2.4代碼4:
筆試題代碼和解析如下:
#include <stdio.h>
#include <string.h>
int main()
{
char arr[] = "abcdef";
printf("%d\n",strlen(arr));//6
printf("%d\n",strlen(arr + 0));//arr首元素的地址,arr + 0還是首元素的地址,向后統計在\0之前的字符個數。//6
printf("%d\n",strlen(*arr));//'a' —— 97,出錯。
printf("%d\n",strlen(arr[1]));//'b' —— 98.出錯。
printf("%d\n",strlen(&arr));//&arr是數組的地址,也是從數組第一個元素開始向后找,6。
printf("%d\n",strlen(&arr + 1));//隨機值。
printf("%d\n",strlen(&arr[0] + 1));//5
//&arr —— char(*)[7]
//size_t strlen(const char*s);
return 0;
}
運行結果如下圖:
2.2.5代碼5:
筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
const char*p = "abcdef";
printf("%d\n",sizeof(p));//p是指針變量,我們計算的是指針變量的大小,4/8個字節。
printf("%d\n",sizeof(p + 1));//p + 1是b的地址,是地址大小就是4/8個字節。
printf("%d\n",sizeof(*p));//p的類型是char*,*p就是char類型了,1個字節。
printf("%d\n",sizeof(p[0]));//1.p[0]->*(p + 0)->*p->'a',大小1個字節。
//1.把常量字符串想象成數組。
//2.p可以理解為數組名,p[0],就是首元素。
printf("%d\n",sizeof(&p));//取出的是p的地址,地址的大小就是4/8個字節。
printf("%d\n",sizeof(&p + 1));//&p + 1是跳過p指針變量后的地址,地址的大小是4/8個字節。
printf("%d\n",sizeof(&p[0] + 1));//4/8,取出首元素的地址,+1是第二個字符的地址。
return 0;
}
運行結果如下圖:
2.2.6代碼6:
筆試題代碼和解析如下:
#include <stdio.h>
#include <string.h>
int main()
{
char*p ="abcdef";
printf("%d\n",strlen(p));//6
printf("%d\n",strlen(p + 1));//5
printf("%d\n",strlen(*p));//*p就是'a' —— 97,err
printf("%d\n",strlen(p[0]));//p[0]->*(p + 0)->*p//err
printf("%d\n",strlen(&p));//&p是指針變量p的地址,和字符串“abcdef”關系就不大了,從p這個指針變量的起始位置開始向后數的,p變量存放的地址是什么,不知道,所以答案是隨機值。
printf("%d\n",strlen(&p + 1));//隨機值
printf("%d\n",strlen(&p[0] + 1));//5
return 0;
}
運行結果如下圖:
2.3 二維數組
二維數組筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
int a[3][4] = {0};
printf("%d\n",sizeof(a));//a是數組名,單獨放在sizeof內部,計算的是數組的大小,單位是字節 —— 48 = 3*4*sizeof(int)。
printf("%d\n",sizeof(a[0][0]));//a[0][0]第一行第一個元素,大小是4個字節。
printf("%d\n",sizeof(a[0]));//a[0]是第一行的數組名,數組名單獨放在sizeof內部了,計算的是數組的總大小16個字節。
printf("%d\n",sizeof(a[0] + 1));//a[0]并沒有單獨放在sizeof內部,所以這里的數組名a[0]就是數組首元素的地址,即a[0]->&a[0][0],+1后是&a[0][1]的地址,大小是4/8個字節。
printf("%d\n",sizeof(*(a[0] + 1)));//第一行第一個元素,大小是4
printf("%d\n",sizeof(a + 1));//a作為數組名并沒有單獨放在sizeof內部,a表示數組首元素的地址,是二維數組首元素的地址,也就是第一行的地址,a + 1,跳過一行,指向了第二行,a + 1是第二行的地址,a + 1是數組指針,是地址大小就是4/8個字節。
printf("%d\n",sizeof(*(a + 1)));//1.a + 1是第二行的地址,*(a + 1)就是第二行,計算的是第二行的大小 —— 16。2.*(a + 1) == a[1],a[1]是第二行的數組名,sizeof(*(a + 1))就相當于sizeof(a[1]),意思就是把第二行的數組名單獨放在sizeof內部,計算的是第二行的大小。
printf("%d\n",sizeof(&a[0] + 1));//a[0]是第一行的數組名,&a[0]取出的就是數組的地址,就是第一行的地址,&a[0] + 1就是第二行的地址,是地址大小就是4/8字節。
printf("%d\n",sizeof(*(&a[0] + 1)));//對第二行地址解引用,訪問的就是第二行,大小是16個字節。
printf("%d\n",sizeof(*a));//a作為數組名并沒有單獨放在sizeof內部,a表示數組首元素的地址,是二維數組首元素的地址,也就是第一行的地址,*a就是第一行,計算的就是第一行的大小,16個字節。
printf("%d\n",sizeof(a[3]));//a[3]無需真實存在,僅僅通過類型的推斷就能算出長度,a[3]是第四行的數組名,單獨放在sizeof內部,計算第四行的大小,16個字節。
//sizeof(int);//4
//sizeof(3+5);//4
return 0;
}
運行結果如下圖:
三、指針運算筆試題解析
3.1題目1:
指針運算筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
int a[5] = {1,2,3,4,5};
int*ptr = (int*)(&a + 1);//&a —— int(*)[5]
printf("%d %d",*(a + 1),*(ptr - 1));//ptr跳過了原來a數組指向下一個位置,*(ptr-1)訪問的就是數組a中的5。
return 0;
}
運行結果如下圖:
3.2題目2:
在X86(32位)環境下,假設結構體的大小是20個字節,程序輸出的結果是什么?
指針運算筆試題代碼和解析如下:
#include <stdio.h>
struct Test
{
int Num;
char*PcName;
short sDate;
char cha[2];
short sBa[4];
}*p = (struct Test*)0x100000;//結構體指針+1,跳過一個結構體;整型值+1,就是+1。
//指針+-整數
int main()
{
printf("%p\n",p + 0x1);//0x100000 + 20 -> 00100014
printf("%p\n",(unsigned long)p + 0x1);//0x100000 + 1 ->0x100001 -> 00100001
printf("%p\n",(unsigned int*)p + 0x1);//0x100000 + 4 -> 0x100004 -> 00100004
return 0;
}
運行結果如下圖:
3.3題目3:
指針運算筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
int a[3][2] = {(0,1),(2,3),(4,5)};//1 3 5//初始化
int*p;
p = a[0];//&a[0][0];
printf("%d",p[0]);//1//*(p + 0) -> *p
return 0;
}
運行結果如下圖:
3.4題目4:
假設環境是X86環境,程序的輸出結果是什么?
指針運算筆試題代碼和解析如下:
#include <stdio.h>
int main()
{//%d —— 是打印有符號的整數
//%p —— 是打印地址的
int a[5][5];//a —— 類型是:int(*)[5]
int(*p)[4];//p —— 類型是:int(*)[4]//p是一個數組指針,p指向的數組是4個整型元素的
p = a;//類型的差異 —— 警告
printf("%p,%d\n",&p[4][2] - &a[4][2],&p[4][2] - &a[4][2]);//FFFFFFFC,-4
return 0;
}
//指針-指針絕對值得到的是指針和指針之間的元素個數
運行結果如下圖:
3.5題目5:
指針運算筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
int aa[2][5] = {1,2,3,4,5,6,7,8,9,10};
int*ptr1 = (int*)(&aa + 1);
int*ptr2 = (int*)(*(aa + 1));
printf("%d %d",*(ptr1 - 1),*(ptr2 - 1));//10 5
return 0;
}
//*(aa + 1)->aa[1],aa[1]是第二行的數組名,數組名表示首元素的地址。
//aa[1]也是&aa[1][0]
//*(aa + 1)->aa[1],&aa[1]->第二行的地址
//sizeof(aa[1])->計算的是第二行的大小
運行結果如下圖:
3.6題目6:
指針運算筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
char*a[] = {"work","at","alibaba"};//a是指針數組
char**pa = a;
pa++;
printf("%s\n",*pa);//at//%s是打印字符串,給一個地址,從這個地址向后打印字符串,直到\0
return 0;
}
畫圖分析:
運行結果如下圖:
3.7題目7:
指針運算筆試題代碼和解析如下:
#include <stdio.h>
int main()
{
char*c[] = {"ENTER","NEW","POINT","FIRST"};
char**cp[] = {c + 3,c + 2, c + 1,c};
char***cpp = cp;
printf("%s\n",**++cpp);//POINT
printf("%s\n",*--*++cpp + 3);//ER
printf("%s\n",*cpp[-2] + 3);//**(cpp - 2) + 3//ST
printf("%s\n",cpp[-1][-1] + 1);//*c*(cpp-1) - 1//EW
return 0;
}
運行結果如下圖:
四、總結
本文深入探討了C語言中關于sizeof
、strlen
、數組和指針的一些基礎概念,并通過代碼示例進行了詳細的講解。以下是對主要內容的總結:
1. sizeof
與strlen
的區別
sizeof
是一個運算符,用于計算變量或類型所占的內存空間大小,單位是字節。它與數據存儲內容無關,只關注內存的占用。例如,sizeof(int)
會返回一個整數類型的大小,而sizeof(a)
會返回數組a
的總字節數。需要注意的是,sizeof
中的表達式不會被計算,僅僅是編譯時確定的常量。
與此不同,strlen
是C標準庫中的一個函數,用于計算以'\0'
(空字符)結尾的字符串的長度。它統計的是字符串中的字符個數,而不包括'\0'
字符。因此,strlen
在處理字符串時,必須確保字符串正確地以'\0'
結尾,否則可能導致越界訪問。
2. 數組和指針的關系
數組和指針是C語言中常見的概念,它們密切相關。數組名通常被認為是指向數組首元素的指針。通過數組名,可以訪問數組的元素,但是數組名和指針在某些情況下也有所不同。例如,sizeof(a)
計算的是整個數組的大小,而sizeof(a + 1)
計算的是數組中某個元素的指針大小。此外,數組名也可以通過&a
表示整個數組的地址,&a[0]
表示數組首元素的地址。
3. 字符數組與字符串
字符數組在內存中的存儲方式可能導致不同的行為。在沒有'\0'
結尾的情況下,使用strlen
函數可能會導致越界訪問,進而產生隨機結果。通過具體的代碼示例,文章展示了不同數組類型在使用sizeof
和strlen
時的差異,特別是字符數組和字符串常量。
4. 指針運算
指針運算是C語言中強大的功能之一。指針可以進行加減操作,指向內存中的不同位置。通過對數組指針進行運算,可以訪問數組中的不同元素。指針間的運算遵循指針類型的大小,比如int*
指針加1時會跳過一個int
類型的大小,指向下一個int
類型的數據。文章通過一系列例子展示了指針和數組在內存中的操作,包括指針的解引用、指針數組的運算等。
總結
本篇文章深入分析了sizeof
、strlen
、數組與指針等概念,并通過一系列代碼示例加深了對這些概念的理解。對于初學者來說,掌握這些基礎知識是學習C語言的關鍵。文章不僅揭示了這些基本概念的使用方法,還通過具體例子幫助理解如何避免常見的錯誤,如越界訪問和指針運算中的誤解。