目錄
1. 指針基本知識(指針初階)
1.1 指針:
1.2 指針類型
1.2.1 指針
1.2.2 指針類型的意義
1.3 野指針
1.3.1 野指針
1.3.2 野指針的産生:
1.3.3 如何規避野指針
1.4 指針運算
1.4.1 指針+-整數
1.4.2 指針比較大小
1.4.3 指針+-指針
1.5 一級指針
1.6 二級指針
2. 指針進階
2.1 字元指針
2.2 指針數組
2.2.1 指針數組
2.2.2 代碼解讀
2.3 數組指針
2.3.1 數組指針:
2.3.2 arr和&arr的差別
2.3.3 數組指針的定義
2.3.4 數組指針的應用
2.3.5 代碼解讀
2.3.6 一維數組傳參
2.3.7 二維數組傳參
2.4 函數指針
2.4.1 函數指針
2.4.2 函數名和&函數名的差別
2.4.3 函數指針的定義
2.4.4 用函數指針調用函數
2.4.5 代碼解讀
2.5 函數指針數組
2.5.1 函數指針數組
2.5.2 函數指針數組的定義
2.5.3 函數指針數組的應用
2.6 位元組的計算
2.6.1 整型數組
2.6.2 字元數組
2.6.3 二維數組
2.7 指針的練習
2.7.1 練習1
2.7.2 練習2
2.7.3 練習3
2.7.4 練習4
2.7.5 練習5
2.7.6 練習6
1. 指針基本知識(指針初階)
1.1 指針:
存放位址的變量稱為指針。
可以通過指針來通路變量,數組,函數等。
1.2 指針類型
1.2.1 指針
指針是一個變量,那麼一定有它自己的類型。指針的類型主要有int*,short*,char*,float*等,還有一些指向數組,結構體,函數等一些對象的類型。
1.2.2 指針類型的意義
1. 指針類型決定了,指針解引用的權限有多大
如:int*類型的指針解引用可以處理四個位元組的空間,
而char*類型的指針解引用隻能處理一個位元組的空間
2. 指針類型決定了,指針走一步,能走多遠(步長)
如:
int main()
{
int a = 3;
char b = '0';
int* pa = &a;//定義整型指針
char* pb = &b;//定義字元指針
int* pa1 = &a;//定義數組指針
char* pa2 = &a;
pa1++;
pa2++;
printf("%p\n", &a);
printf("%p\n", pa1);
printf("%p\n", pa2);
return 0;
}
運作結果如下:
對指針的定義如上述程式所示,分别定義了整型指針和字元型指針,是将所指變量的位址存入指針變量之中。
分别把整型變量a的位址分别存放在整型指針變量pa1和字元型指針變量pa2中,然後對這兩個指針變量分别進行加一操作,結果如上圖所示。
整型變量a的位址是0x0056F8F4,将a的位址存放到整型指針變量pa1和字元型指針變量pa2中,兩個指針變量分别進行加一操作,因為指針變量pa1的類型是int*,是以pa1的一步可以走4個位元組,是以pa1+1後的位址是在變量a的位址0x0056F8F4上加了4個位元組,即0x0056F8F8。
同理可得,因為指針變量pa2的類型是char*,是以pa2的一步可以走1個位元組,是以pa2+1後的位址是在變量a的位址0x0056F8F4上加了4個位元組,即0x0056F8F5。
其中%p是列印位址
1.3 野指針
1.3.1 野指針
野指針就是指針指向的位置是不可知的(随機的、不正确的、沒有明确限制的)。
1.3.2 野指針的産生:
1.指針未初始化
2.指針越界通路
3.指針指向的空間釋放
1.3.3 如何規避野指針
1.指針初始化
當不知道指針具體位址時,可以将指針初始化為0或者NULL(void *)
2.小心指針越界
3.指針指向空間釋放及時置NULL
當指針所指的空間已經還給系統時,将指針置NULL
4.指針使用之前檢查有效性
指針置為NULL時也不是有效指針,是以使用前必須檢查其有效性
if (p!=NULL)
*p=10;
1.4 指針運算
1.4.1 指針+-整數
指針根據其指針類型,向前或向後移動。
1.4.2 指針比較大小
比較兩個指針所存儲的位址的大小。
标準規定:允許指向數組元素的指針與指向數組最後一個元素後面的那個記憶體位置指針比較,但是不允許與指向第一個元素之前的那個記憶體位置的指針比較,即向前不行,向後可以。
1.4.3 指針+-指針
指針和指針相減的前提是,兩個指針指向同一個空間,兩個指針相減得到的是兩個指針所指的兩個空間中間的元素個數。
1.5 一級指針
int arr[3]={1,2,3};
int* p=arr;
arr[2]<==>*(arr+2)<==>*(p+2)<==>*(2+p)<==>*(2+arr)<==>2[arr]
arr[2]==p[2]
存在一個數組arr[3],數組名arr表示的是數組首元素的位址,也就是數組元素1的位址,現将數組首元素的位址存放到整型指針變量之中,那麼将進行以下的推導:
arr[2]表示的是數組第三個元素,即3,數組名arr表示的是數組首元素的位址,那麼arr+2表示的便是數組元素3的位址,再對arr+2進行解引用操作,那麼*(arr+2)表示的便是數組元素3,與arr[2]表示的内容一緻。
同樣,前面将數組首元素的位址存放到整型指針變量p之中,那麼整型指針變量p與數組名arr所表示的内容是一樣的,是以*(arr+2)與*(p+2)所表示的内容相同,都是數組的第三個元素3。
因為加法存在交換律,那麼*(arr+2)與*(p+2)可以寫作*(2+arr)與*(2+p),那麼就可以寫成2[arr]。
根據上述的推導,可以得到arr[2]和p[2]的意義相同,都表示數組的第三個元素3。
1.6 二級指針
int a=10;
int* pa=&a;
int* *ppa=&pa;
//*pa==a *ppa==pa ——>**ppa==a
如上,将整型變量a的位址存放在一級整型指針變量pa之中,将指針pa的位址再存放在指針ppa之中,那麼指針ppa就是二級指針。
對一級指針pa進行解引用操作後,指向的是整型變量a。
對二級指針ppa進行一次解引用操作之後,指向的是一級指針變量pa。
對二級指針ppa進行兩次解引用操作之後,指向的便是整型變量a。
2. 指針進階
2.1 字元指針
char* ps=“hello bit”;//此處的字元串為常量字元串,不能修改
printf(“%s”,ps);//可以列印出整個字元串
字元指針可以直接指向一個字元串,如上。其本質上是把字元串的首字元的位址存儲到了字元型指針變量ps中,列印的時候可以通過列印字元串全部列印出來。
%s是用來列印字元串的。
2.2 指針數組
2.2.1 指針數組
定義:用來存儲指針變量的數組。
指針數組也是數組。
#include<stdio.h>
int main()
{
int a[5] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = { 3,4,5,6,7 };
int* arr[3] = { a,b,c };//将三個數組的首元素的位址放在指針數組中
int i = 0;
for (i = 0; i < 3; i++)
{
int j = 0;
for (j = 0; j < 5; j++)
{
printf("%d ", *(arr[i] + j));//指針數組的通路方式1
printf("%d ", arr[i][j]);//指針數組的通路方式2
}
printf("\n");
}
}
如上,定義三個整型數組,将數組首元素的位址放入一個數組arr中,那麼這個數組arr就是指針數組,數組類型為int*,裡面有三個元素。
arr[i]表示的是指針數組中的第i+1個元素,例如當i=0的時候,arr[0]表示的便是指針數組arr的第一個元素,而第一個元素是a,是數組a的首元素的位址,對首位址元素的位址進行加法操作,就可以得到數組a中其他元素的位址,例如arr[0]+1表示的便是數組a的第二個元素的位址,再對該位址進行解引用操作,便可以得到數組a的第二個元素2,即*(arr[0]+1)==2。是以通路數組元素的時候可以通過指針數組通路,如上,即*(arr[i]+j)。
數組元素通過指針數組來通路還可以用arr[i][j]來通路。該通路方式與二維數組的通路方式相同,但隻是模拟出二維數組,實際上與二維數組不同。
2.2.2 代碼解讀
對已學過的數組類型辨認:
int* arr1[10];
char* arr2[4];
char** arr3[5];
第一行是一個整型指針數組,數組裡面存放的是10個整型指針;
第二行是一個一級字元型指針數組,數組裡面存放的是4個一級字元型指針;
第三行是一個二級字元型指針數組,數組裡面存放的是5個二級字元型指針;
2.3 數組指針
2.3.1 數組指針:
指向一個數組的指針變量叫做數組指針。數組指針是一個指針。
2.3.2 arr和&arr的差別
兩者的值相同,但意義不一樣。
arr表示的是數組首元素的位址,加一之後表示的是數組第二個元素的位址;
&arr是數組的位址,加一之後是跳過了整個數組。
2.3.3 數組指針的定義
int arr[10] = { 1,2,3,4,5 };
int (*parr)[10] = &arr;//arr是數組的首元素的位址,&arr是數組的位址
//parr是一個數組指針,裡面存放的是數組的位址
double* d[5];//建立一個指針數組
double* (*pa)[5] = &d;//pa是一個數組指針,裡面存放指針數組d的位址
//double*是表示數組裡面内容的格式,*pa是表示pa是一個指針,[5]表示所指數組有5個元素
首先定義一個整型數組arr,取該數組的位址放入數組parr中,則parr數組指針的類型是int (*)[10],但由于文法要求,就寫成如下的形式:int (*parr)[10]。其中*表示parr是一個指針變量;[10]表示parr指向的是一個數組,即表示parr是一個數組指針,10表示parr所指的數組有10個元素;而int表示的是parr所指數組之中的元素的類型都為int類型。
同理可得,先建立一個double*類型的指針數組d,那麼建立一個指向該指針數組的數組指針pa,首先取指針數組的位址放入數組指針變量pa之中,而pa的類型需要根據指針數組d來确定。首先pa是一個指針變量,是以表示為(*pa),再者pa所指向的指針數組d有5個元素,是以表示其為(*pa)[5],最後由于指針數組d之中的5個元素都是double*元素,那麼就表示為double* (*pa)[5]。
在C語言中,數組名表示的是首元素的位址,但是有兩個例外:
1.sizeof(數組名)——數組名表示整個數組,計算的是整個數組的大小,機關是位元組;
2.&數組名——數組名表示整個數組,取出的是整個數組的位址。
2.3.4 數組指針的應用
1. 用數組指針通路一維數組
int main()
{
int arr[10] = { 0,1,2,3,4,5,6,7,8,9 };
int(*parr)[10] = &arr;
int i = 0;
for (i = 0; i < 10; i++)
{
printf("%d ", *((*parr) + i));//*parr相當于數組名,也可寫作*(*parr + i)
}
return 0;
}
如上,先建立一個指向整型數組arr的數組指針parr,然後通過parr這個數組指針來通路一維整型數組arr。
parr裡面存放的是數組a的位址,那麼對parr進行解引用操作,即(*parr),此時表示的是數組名arr,數組名arr表示的是數組arr首元素的位址,是以(*parr)表示的便是數組首元素的位址,相當于數組名arr。要通路整個數組arr的元素,那麼就在(*parr)的基礎上加上相對于數組首元素位址的偏移量,上述代碼的位址相對偏移量為i,故(*parr) + i表示的便是數組arr中第i+1個元素的位址,再對其進行解引用操作,即*((*parr) + i),便可以得到數組arr的第i+1個元素。
2.用數組指針通路二維數組
void print(int(*parr)[5], int r, int c)
{
int i = 0;
for (i = 0; i < r; i++)//行
{
int j = 0;
for (j = 0; j < c; j++)//列
{
printf("%d ", *(*(parr + i) + j));
}
printf("\n");//列印完一行後換行
}
}
int main()
{
int arr[3][5] = { {1,2,3,4,5},{2,3,4,5,6},{3,4,5,6,7} };
print(arr, 3, 5);
return 0;
}
二維數組可以看成幾個一維數組組成的數組,例如二維數組arr[3][5],可以看成三個一維數組組成的一個數組,即arr[3] [5],arr[3]可以看成一維數組的數組名,每個一維數組之中有5個元素。這樣看的話,二位數組的首元素為數組的第一個一維數組,也就是二維數組的第一行。而數組名表示的是數組首元素的位址,二維數組也不例外,是以二維數組的數組名表示的就是二維數組第一行元素的位址,也是第一個一維數組的位址,是以arr和arr[0]表示的内容相同,都是二維數組的第一行元素的位址。綜上所述,二維數組傳遞參數時,便可以用一個數組指針接收。
首先定義一個二維數組arr,通過函數print列印整個二維數組,print函數的所傳遞的參數為二維數組的數組名,二維數組的行數,二維數組的列數,接收的時候用整型數組指針接收二維數組,兩個整型變量分别接收二維數組的行數和列數。
二維數組傳遞的是數組名,接收的是一個數組指針,那麼數組名arr與數組指針parr等價,表示的都是二維數組第一行的位址,而parr+i就相當于arr+i,表示的是二維數組的第i+1行的位址,也是第i+1個一維數組的位址,對其解引用操作,即*(parr+i),*(parr+i)相當于該二維數組第i+1個一維數組的數組名,是以也表示為該一維數組的首元素的位址,再通過加上相對于數組首元素位址的偏移量,即*(parr + i) + j,就可以得到二維數組中任何一個元素的位址,再對其進行解引用操作,即 *(*(parr + i) + j),便可以得到二維數組的任何一個元素。
2.3.5 代碼解讀
指出下列代碼表示的内容:
int arr[5];//1
int* parr1[10];//2
int(*parr2)[10];//3
int(*parr3[10])[5];//4
1. int arr[5]
表示一個數組,可以存放5個元素,每個元素為int類型。
2. int* parr1[10]
表示一個指針數組,可以存放10個指針,每個指針指向的内容為int類型。
3. int(*parr2)[10]
表示一個數組指針,指向一個數組,數組可以存放10個元素,每個元素為int類型。
4. int(*parr3[10])[5]
表示一個存放數組指針的數組,數組可以存放10個數組指針,每個指針都指向一個數組,每個數組包含5個元素,每個元素為int類型。
2.3.6 一維數組傳參
int arr1[10]={0};
int* arr2[10]={0};
test1(arr1);
test2(arr2);
void test1(int arr1[10])
void test1(int arr1[])
void test1(int* arr)
void test2(int* arr[10])
void test2(int**arr)
定義一個一維整型數組和一個一維整型指針數組,分别作為函數參數進行傳參,有如上5種方式接收。
一維整型數組傳參,可以用一維整型數組接收,其中數組元素數量可以省略,也可以用一個一級整型指針接收;
一維整型指針數組傳參,可以用一個一維整型指針數組接收,也可以用一個二級整型指針接收。
2.3.7 二維數組傳參
int arr [3][5]={0};
int arr [][5]={0}; //行可以省略,列不可以省略
test(arr);
void test(int arr[3][5])
void test(int arr[ ][5])
void test(int (*arr)[5])
二維數組作為函數參數進行傳參時,可以用一個相同大小的二維數組接收,其中二維數組的行數可以省略,但列數不可以省略;也可以用一個一級數組指針接收(原因見本文2.3.4中第2點)。
2.4 函數指針
2.4.1 函數指針
指向函數的指針 ,存放函數位址的指針叫做函數指針。
2.4.2 函數名和&函數名的差別
數組名!=&數組名(數值相同,意義不同)
函數名==&函數名(數值相同,意義相同)
2.4.3 函數指針的定義
int ADD(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &ADD;//pf是一個函數指針
//int (*pf)(int, int) = ADD;//&ADD==ADD==pf
printf("%p\n", ADD);
printf("%p\n", &ADD);//兩者意義數字都相同
return 0;
}
有一個函數ADD,定義一個函數指針指向函數ADD。
首先是将函數ADD的位址取出來,存放在函數指針pf之中,*pf表示pf是一個指針,在後面加上小括号,表示這個指針是函數指針,小括号内部是pf所指函數的參數類型,此處pf所指的函數是ADD,其參數有兩個,類型都是int類型,是以表示為(*pf)(int, int),而ADD函數還有傳回類型,其傳回類型為int,是以這個函數指針的定義表示為:int (*pf)(int, int) 。
而後輸出函數名和&函數名,觀察差別。
運作程式,得到運作結果如下圖:
由此可見, 函數名==&函數名(數值相同,意義相同)。
2.4.4 用函數指針調用函數
int ADD(int x, int y)
{
return x + y;
}
int main()
{
int (*pf)(int, int) = &ADD;//pf是一個函數指針
//int (*pf)(int, int) = ADD;//&ADD==ADD==pf
int ret = (*pf)(3, 5);//第一種:函數指針調用函數
int ret = pf(3, 5);//第二種:函數指針調用函數,*是為了了解,沒有實際的意義
int ret = ADD(3, 5);//第三種:函數指針調用函數
printf("%d\n", ret);
return 0;
}
根據2.4.3所述,定義了函數指針,此處用函數指針來調用函數。
第一種:用(*pf)(參數1,參數2)便可以調用ADD函數;
第二種:用pf(參數1,參數2)也可以調用ADD函數;
第三種:是我們正常調用函數的方法,函數名(參數1,參數2)。
由上述三種方法對比,可以得到pf與(*pf)等價,是以函數指針中的解引用操作符*是為了了解才存在的,沒有實際意義,可以省略不寫。
2.4.5 代碼解讀
(*(void (*)())0)();//1
void (*signal(int, void(*)(int)))(int);//2
1. (*(void (*)())0)();
作用:調用0位址處的函數。
該函數無參數,傳回類型為void
從裡向外解讀如下:
void(*)()--函數指針類型,void(*p)()--函數指針變量
(void(*)())0--對0進行強制類型轉化,轉化為函數指針類型
*(void(*)())0--對0位址進行了解引用操作
(*(void(*)())0)()--調用0位址處的函數
2.void (*signal(int, void(*)(int)))(int);
作用:函數聲明
等價于 void(*)(int) signal(int,void(*)(int))
函數signal的函數參數類型分别是int類型和void(*)(int)函數指針類型,傳回類型是void(*)(int)
但文法要求不能這樣寫,是以将函數放*一起
對代碼2進行簡化,如下
typedef void(*pfun_t)(int);//重新定義void(*)(int)函數指針類型為pfun_t
pfun_t signal(int,void(*)(int));
2.5 函數指針數組
2.5.1 函數指針數組
定義:存放函數指針的數組叫做函數指針數組。
函數指針數組也是數組。
2.5.2 函數指針數組的定義
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int main()
{
int(*pf1)(int, int) = Add;//定義函數指針變量pf1
int(*pf2)(int, int) = Sub;//定義函數指針變量pf2
int(*pfarr[2])(int, int) = { Add,Sub };//pfarr就是函數指針數組
return 0;
}
首先定義兩個函數Add和Sub,然後再定義兩個函數指針pf1和pf2分别指向函數Add和Sub,再建立一個函數指針數組,建立過程如下。
pfarr是定義的函數指針數組的數組名,該數組裡面的元素是函數指針,是以pfarr函數指針數組的類型是int(*)(int, int),[2]表示的是該數組的元素個數,是以定義的函數指針數組為int(*pfarr[2])(int, int)。
2.5.3 函數指針數組的應用
寫一個可以實作加減乘除的電腦程式。
第一種:用switch語句實作電腦加減乘除的選擇,如下
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
return x / y;
}
void meau()
{
printf("*****************************************\n");
printf("********** 1.add 2.sub **********\n");
printf("********** 3.mul 4.div **********\n");
printf("********** 0.退出系統 **********\n");
printf("*****************************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
meau();
printf("請選擇:");
scanf("%d", &input);
switch (input)
{
case 1:
printf("請輸入兩個操作數:");
scanf("%d %d", &x, &y);
ret = add(x, y);
printf("ret=%d\n", ret);
break;
case 2:
printf("請輸入兩個操作數:");
scanf("%d %d", &x, &y);
ret = sub(x, y);
printf("ret=%d\n", ret);
break;
case 3:
printf("請輸入兩個操作數:");
scanf("%d %d", &x, &y);
ret = mul(x, y);
printf("ret=%d\n", ret);
break;
case 4:
printf("請輸入兩個操作數:");
scanf("%d %d", &x, &y);
ret = div(x, y);
printf("ret=%d\n", ret);
break;
case 0:
printf("退出系統\n");
break;
default:
printf("輸入錯誤,請重新輸入\n");
break;
}
printf("\n\n");
} while (input);
return 0;
}
第二種:用函數指針數組實作電腦加減乘除的選擇,如下:
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
return x / y;
}
void meau()
{
printf("*****************************************\n");
printf("********** 1.add 2.sub **********\n");
printf("********** 3.mul 4.div **********\n");
printf("********** 0.退出系統 **********\n");
printf("*****************************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
//轉移表
int(*p[5])(int, int) = { 0,add,sub,mul,div };
do
{
meau();
printf("請選擇:");
scanf("%d", &input);
if (input >= 1 && input < 5)
{
printf("請輸入兩個操作數:");
scanf("%d %d", &x, &y);
ret = (*p[input])(x, y);
printf("ret=%d\n", ret);
}
else if (input == 0)
{
printf("退出系統\n");
break;
}
else
{
printf("輸入錯誤,請重新輸入\n");
break;
}
printf("\n\n");
} while (input);
return 0;
}
第三種:用函數回調的方法實作電腦加減乘除的選擇,如下:
//電腦
//3
void cal(int(*p)(int, int))
{
int x = 0;
int y = 0;
int ret = 0;
printf("請輸入兩個操作數:");
scanf("%d %d", &x, &y);
ret = (*p)(x, y);
printf("ret=%d\n", ret);
}
int add(int x, int y)
{
return x + y;
}
int sub(int x, int y)
{
return x - y;
}
int mul(int x, int y)
{
return x * y;
}
int div(int x, int y)
{
return x / y;
}
void meau()
{
printf("*****************************************\n");
printf("********** 1.add 2.sub **********\n");
printf("********** 3.mul 4.div **********\n");
printf("********** 0.退出系統 **********\n");
printf("*****************************************\n");
}
int main()
{
int input = 0;
int x = 0;
int y = 0;
int ret = 0;
do
{
meau();
printf("請選擇:");
scanf("%d", &input);
switch (input)
{
case 1:
cal(add);
break;
case 2:
cal(sub);
break;
case 3:
cal(mul);
break;
case 4:
cal(div);
break;
case 0:
printf("退出系統\n");
break;
default:
printf("輸入錯誤,請重新輸入\n");
break;
}
printf("\n\n");
} while (input);
return 0;
}
回調函數就是一個通過函數指針調用的函數。如果你把函數的指針(位址)作為參數傳遞給另外一個函數,當這個指針被用來調用其所指向的函數時,我們就說這是回調函數。回調函數不是由該函數的實作方直接調用的,而是在特定的事件或條件發生時由另外一方調用的,用于對該事件或條件進行響應。
2.6 位元組的計算
2.6.1 整型數組
int main()
{
int a[] = { 1,2,3,4 };
printf("%d\n", sizeof(a));//16--計算的是數組a的長度,有4個元素,為整型類型,4個位元組
printf("%d\n", sizeof(a + 0));//4/8--此處a表示的是數組首元素的位址,計算的是位址的大小
//32位元素的是4,64位的是8
printf("%d\n", sizeof(*a));//4--此處計算的是數組的第一個元素的大小,為整型,占4個位元組
printf("%d\n", sizeof(a + 1));//4/8--此處a表示的是數組首元素的位址,計算的是數組第二個元素的位址的大小
printf("%d\n", sizeof(a[1]));//4--此處計算的是第二個元素的大小,為整型,占4個位元組
printf("%d\n", sizeof(&a));//4/8--此處計算的是數組的位址的大小,但也是位址
printf("%d\n", sizeof(*&a));
//16--此處對數組取位址,獲得數組位址,解引用後指向的是一個數組,是以此處計算的是數組的大小
printf("%d\n", sizeof(&a + 1));//4/8--此處計算的是數組之後的空間的位址的大小
printf("%d\n", sizeof(&a[0]));//4/8--此處計算的是數組第一個元素的位址
printf("%d\n", sizeof(&a[0] + 1));//4/8--此處計算的是數組第二個元素的位址
return 0;
}
2.6.2 字元數組
#include<string.h>
int main()
{
char arr[] = { 'a','b','c','d','e','f' };//空間儲存的是 a b c d e f
printf("%d\n", sizeof(arr));
//6--數組名單獨在sizeof之中,數組名表示的是整個數組,計算的是整個數組的長度,故為6
printf("%d\n", sizeof(arr + 0));
//4/8--數組名沒有單獨在sizeof之中,是以數組名表示的是數組首元素的位址
//數組首元素的位址加0後,表示的還是數組首元素的位址
//是以sizeof計算的是數組首元素位址的長度,在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(*arr));
//1--數組名沒有單獨在sizeof之中,是以數組名表示的是數組首元素的位址
//解引用之後就是數組的第一個元素a,是以sizeof計算的是元素a的長度,a為char類型,占一個位元組的空間
printf("%d\n", sizeof(arr[1]));
//1--同上,sizeof計算的是元素a的長度,a為char類型,占一個位元組的空間
printf("%d\n", sizeof(&arr));
//4/8--sizeof計算的是數組的位址的大小,因為計算的還是位址的大小
//是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(&arr + 1));
//4/8--計算的是數組之後與該數組大小相同的空間的位址的大小,究其本質,計算的還是位址的大小
//是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(&arr[0] + 1));
//4/8--計算的是數組第二個元素的位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", strlen(arr));
//随機值--arr表示數組的第一個元素,表示從數組的第一個元素開始計算字元數,直到遇到\0為止
//但該數組初始化的時候沒有存儲\0,是以\0的位置不知道在哪裡,是以計算結果為随機值
printf("%d\n", strlen(arr + 0));
//随機值--同上,也是從數組的第一個元素開始計算字元數,因為\0的位置不知道在哪裡,是以計算結果為随機值
printf("%d\n", strlen(*arr));
//錯誤--操作數應該是一個位址,此時操作數為數組首元素a,運作程式的時候會報錯
printf("%d\n", strlen(arr[1]));
//錯誤--操作數應該是一個位址,此時操作數為數組元素b,運作程式的時候會報錯
printf("%d\n", strlen(&arr));
//随機值--從數組的位址開始計算字元數,也就是數組首元素的位址
//因為\0的位置不知道在哪裡,是以計算結果為随機值
printf("%d\n", strlen(&arr + 1));
//随機值--從數組之後與該數組大小相同的空間的位址開始計算字元數
//也就是該數組最後一個元素的位址加一位元組的位址開始計算字元數
//因為\0的位置不知道在哪裡,是以計算結果為随機值
printf("%d\n", strlen(&arr[0] + 1));
//随機值--從數組第二個元素的位址開始計算,因為\0的位置不知道在哪裡,是以計算結果為随機值
return 0;
}
#include<string.h>
int main()
{
char arr[] = "abcdef";//空間存儲的是 a b c d e f \0
printf("%d\n", sizeof(arr));
//7--數組名單獨在sizeof之中,數組名表示的是整個數組,計算的是整個數組的長度,故為7
printf("%d\n", sizeof(arr + 0));
//4/8--數組名沒有單獨在sizeof之中,是以數組名表示的是數組首元素的位址
//數組首元素的位址加0後,表示的還是數組首元素的位址
//是以sizeof計算的是數組首元素位址的長度,在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(*arr));
//1--數組名沒有單獨在sizeof之中,是以數組名表示的是數組首元素的位址
//解引用之後就是數組的第一個元素a,是以sizeof計算的是元素a的長度,a為char類型,占一個位元組的空間
printf("%d\n", sizeof(arr[1]));
//1--同上,sizeof計算的是元素a的長度,a為char類型,占一個位元組的空間
printf("%d\n", sizeof(&arr));
//4/8--sizeof計算的是數組的位址的大小,因為計算的還是位址的大小
//是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(&arr + 1));
//4/8--計算的是數組之後與該數組大小相同的空間的位址的大小,究其本質,計算的還是位址的大小
//是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(&arr[0] + 1));
//4/8--計算的是數組第二個元素的位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", strlen(arr));
//6--arr表示數組的第一個元素,表示從數組的第一個元素開始計算字元數,直到遇到\0為止,\0不計算在内
printf("%d\n", strlen(arr + 0));
//6--同上,也是從數組的第一個元素開始計算字元數,是以計算結果為6
printf("%d\n", strlen(*arr));
//錯誤--操作數應該是一個位址,此時操作數為數組首元素a,運作程式的時候會報錯
printf("%d\n", strlen(arr[1]));
//錯誤--操作數應該是一個位址,此時操作數為數組元素b,運作程式的時候會報錯
printf("%d\n", strlen(&arr));
//6--從數組的位址開始計算字元數,也就是數組首元素的位址,是以計算結果為6
printf("%d\n", strlen(&arr + 1));
//随機值--從數組之後與該數組大小相同的空間的位址開始計算字元數
//也就是該數組最後一個元素的位址加一位元組的位址開始計算字元數
//因為\0的位置不知道在哪裡,是以計算結果為随機值
printf("%d\n", strlen(&arr[0] + 1));
//5--從數組第二個元素的位址開始計算,因為\0的位置不知道在哪裡,是以計算結果為5
return 0;
}
#include<string.h>
int main()
{
char* p = "abcdef";//空間存儲的是 a b c d e f \0
printf("%d\n", sizeof(p));
//4/8--p為字元型指針變量,裡面存儲的是字元串的位址,是以計算的是位址的大小
//是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(p + 1));
//4/8--p為字元型指針變量,裡面存儲的是字元串的位址,實際存儲的是字元串首元素的位址,即字元a的位址
//因為計算的是位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(*p));
//1--p為字元型指針變量,裡面存儲的是字元串的位址,實際存儲的是字元串首元素的位址,即字元a的位址
//解引用操作後,*p指的是字元a,是以此處計算的是字元a的大小,計算結果為1
printf("%d\n", sizeof(p[0]));
//1--同上,p[0]表示的是a,是以此處計算的是字元a的大小,計算結果為1
printf("%d\n", sizeof(&p));
//4/8--此處計算的是字元型指針變量p的位址的大小
//因為計算的是位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(&p + 1));
//4/8--此處計算的是字元型指針變量p的位址加一位元組後位址的大小
//因為計算的是位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(&p[0] + 1));
//4/8--此處計算的是字元串第二個字元的位址的大小
//因為計算的是位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", strlen(p));
//6--p為字元型指針變量,裡面存儲的是字元串的位址,實際存儲的是字元串首元素的位址,即字元a的位址
//此處從字元a開始計算字元數,遇到\0為止,是以計算結果為6
printf("%d\n", strlen(p + 1));
//5--p為字元型指針變量,裡面存儲的是字元串的位址,實際存儲的是字元串首元素的位址,即字元a的位址
//此處從字元b開始計算字元數,遇到\0為止,是以計算結果為5
printf("%d\n", strlen(*p));
//錯誤--操作數應該是一個位址,此時操作數為字元串首元素a,運作程式的時候會報錯
printf("%d\n", strlen(p[0]));
//錯誤--操作數應該是一個位址,此時操作數為字元串首元素a,運作程式的時候會報錯
printf("%d\n", strlen(&p));
//随機值--此處從字元型指針變量p的位址開始計算字元數,因為\0的位置不知道在哪裡,是以計算結果為随機值
printf("%d\n", strlen(&p + 1));
//随機值--此處從字元型指針變量p的位址後一位元組處開始計算字元數
//因為\0的位置不知道在哪裡,是以計算結果為随機值
printf("%d\n", strlen(&p[0] + 1));
//5--此處從字元b開始計算字元數,遇到\0為止,是以計算結果為5
return 0;
}
2.6.3 二維數組
int main()
{
int a[3][4] = { 0 };
printf("%d\n", sizeof(a));
//48--數組名單獨在sizeof之中,數組名表示的是整個數組,計算的是整個數組的長度
//整個數組有12個整型元素,一個整型元素占4個位元組,是以整個數組的大小為12*4==48
printf("%d\n", sizeof(a[0][0]));
//4--此處計算的是二維數組的第一行第一列的元素的大小,該元素為整型,是以計算結果為4
printf("%d\n", sizeof(a[0]));
//16--此處計算的是二維數組的首元素的大小,二維數組的首元素為二維數組的第一行
//第一行有4個元素,每個元素為整型,是以計算結果為4*4==16
printf("%d\n", sizeof(a[0]+1));
//4/8--在二維數組中,可以将二維數組的每一行看成一個一維數組,那麼a[0]就是二維數組第一個一維數組的數組名
//而數組名可以表示數組首元素的位址,是以此處計算的是第一行第二列元素的位址的大小
// 因為計算的是位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(*(a[0]+1)));
//4--根據上述,(a[0]+1)表示的是二維數組第一行第二列元素的位址,對其解引用,得到的就是二維數組第一行第二列元素
//此處計算的是二維數組的第一行第二列的元素的大小,該元素為整型,是以計算結果為4
printf("%d\n", sizeof(a+1));
//4/8--數組名沒有單獨在sizeof之中,是以數組名表示的是數組首元素的位址,是以此處計算的是二維數組第二個元素的位址
// 因為計算的是位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(*(a+1)));
//16--根據上述,(a+1)表示的是二維數組第二個元素的位址,解引用後得到的是二維數組的第二個元素
//此處計算的是二維數組的第二個元素的大小,二維數組的第二個元素為二維數組的第二行
//第二行有4個元素,每個元素為整型,是以計算結果為4*4==16
printf("%d\n", sizeof(&a[0]+1));
//4/8--&a[0]表示的是二維數組的首元素的位址,&a[0]+1表示的就是二維數組第二個元素的位址
// 因為計算的是位址的大小,是以在32位環境中計算結果為4,在64位的環境下計算結果為8
printf("%d\n", sizeof(*(&a[0]+1)));
//16--&a[0]表示的是二維數組的首元素的位址,&a[0]+1表示的就是二維數組第二個元素的位址
//解引用後得到的是二維數組的第二個元素
//此處計算的是二維數組的第二個元素的大小,二維數組的第二個元素為二維數組的第二行
//第二行有4個元素,每個元素為整型,是以計算結果為4*4==16
printf("%d\n", sizeof(*a));
//16--數組名沒有單獨在sizeof之中,是以數組名表示的是數組首元素的位址,解引用後得到的是二維數組的首元素
//此處計算的是二維數組的首元素的大小,二維數組的首元素為二維數組的第一行
//第一行有4個元素,每個元素為整型,是以計算結果為4*4==16
printf("%d\n", sizeof(a[3]));
//16--此處計算的是二維數組的第四個元素,但該二維數組隻有三個元素,這屬于越界通路
//但一個變量有兩個屬性,一個是值數組,一個是類型屬性,此處就是根據a[3]的類型屬性來計算出的結果
return 0;
}
2.7 指針的練習
2.7.1 練習1
int main()
{
int a[5] = { 1,2,3,4,5 };
int* p = (int*)(&a + 1);
printf("%d,%d", *(a + 1), *(p - 1));
return 0;
}
分析:
該代碼首先定義了一個具有5個元素的整型數組a,然後取數組a的位址,加上1,此時的位址指向的是該數組最後一個元素5後面一個整型空間,然後将該位址強制轉換為int*類型,存放在整型指針變量p中。然後要列印兩個整型數,分别是 *(a + 1)和 *(p - 1)。
第一個:a為數組名,表示數組的首元素的位址,a+1表示的就是數組第二個元素的位址,對其解引用後,得到數組的第二進制素,為2,然後列印出2;
第二個:p為整型指針變量,指向的是數組a最後一個元素5後面一個整型空間,p-1表示的是數組的第五個元素的位址,解引用後,得到數組的第五個元素,為5,然後列印出5。
運作程式,得到下圖,分析結果無誤。
2.7.2 練習2
struct test
{
int num;
char* name;
short date;
char a[2];
short b[4];
}*p;
//假設p的值為0x100000。
//已知結構體test類型的變量大小是20個位元組
int main()
{
printf("%p\n", p + 0x1);//0x100014
printf("%p\n", (unsigned long)p + 0x1);//0x100001
printf("%p\n", (unsigned int*)p + 0x1);//0x100004
return 0;
}
分析:
首先建立了一個結構體類型struct test以及一個結構體指針*p。
根據題目假設,p的值為0x100000,第一個輸出的是p+0x1,即p+1,p的類型是結構體指針類型,加一就是加了一個結構體的大小,是以就是加20個位元組,寫作16進制就是0x100014。
第二個輸出:首先将結構體指針類型的p強制轉換為unsigned long,那麼p就隻是一個無符号長整形的變量,加一就是加一,是以輸出為0x100001。
第三個輸出:首先将結構體指針類型的p強制轉換為unsigned int*,那麼p加一就是加一個int類型的大小,就是加4個位元組,是以輸出為0x100001。
2.7.3 練習3
int main()
{
int a[4] = { 1,2,3,4 };
int* ptr1 = (int*)(&a + 1);
int* ptr2 = (int*)((int)a + 1);
printf("%x %x", ptr1[-1], *ptr2);
return 0;
}
分析:
首先定義了一個有4個元素的整型數組a,然後取數組位址加一,加的是整個數組的大小,再将數組指針的類型強制轉化為int*類型,賦給整型指針變量ptr1,指向的位址如圖所示。
編譯環境為小端位元組序存儲,那麼數組a在記憶體中如圖所示存儲,a表示的是數組a首元素的位址,如圖,強制轉化為int類型再加一,就隻是整數計算的加一,然後再強制轉換為int*類型指派給整型指針ptr2,所指的位址如圖所示。
%x是輸出16進制數。
ptr[-1]可以寫成*(ptr-1),ptr-1指向的就是數組a中元素4的位址,再進行解引用得到4,按16進制輸出4。由上述分析得到ptr2指向的位置,因為ptr2是int*類型,是以向後讀取4個位元組,解引用得到20000000,按16進制輸出為20000000。
運作程式,結果如下圖:
2.7.4 練習4
int main()
{
int a[3][2] = { (0,1),(2,3) ,(4,5) };
int* p;
p = a[0];
printf("%d\n", p[0]);
return 0;
}
分析:
首先是(0,1),逗号表達式的計算結果為1,同理得3和5,是以數組a在記憶體中的存儲如上圖。a[0]表示的是二維數組的首元素,即二維數組的第一行,将第一行看作一個一維數組,那麼a[0]就是這個一維數組的數組名。p = a[0]就是将一維數組的首元素位址指派給整型指針p,是以p指向的是二維數組元素1的位址,輸出p[0],就是輸出*(p+0),結果為1。
程式運作結果如下:
2.7.5 練習5
int main()
{
int a[5][5];
int(*p)[4];
p = a;
printf("%p %d\n", &p[4][2] - &a[4][2], &p[4][2] - &a[4][2]);
return 0;
}
分析:
a為5行5列的二維數組,存儲如圖。p為指向有4個元素的整型數組指針,将數組a的首元素位址給p,但p指向的數組最多隻有4個元素,是以如上圖所示。
根據圖可知,&p[4][2] - &a[4][2]計算結果為-4,分别以位址的形式和整型的形式輸出。
-4
原碼:10000000 00000000 00000000 00000100
反碼:111111111 111111111 111111111 111111011
補碼:111111111 111111111 111111111 111111100
運作程式,結果如下:
2.7.6 練習6
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\n", *(ptr1 - 1), *(ptr2 - 1));
return 0;
}
分析:
建立一個二維數組aa ,二維數組的存儲如圖所示,&aa + 1是取二維數組的位址加一,加的是二維數組的大小,再強制轉化為int*類型指派給ptr1,是以ptr1指向的位址如圖。*(ptr1 - 1)就是向前移動一個int的大小,即指向元素10,解引用輸出10;*(aa + 1)表示的是二維數組首元素的位址加一,也就是二維數組的第二個元素的位址,解引用得到二維數組aa的第二個元素,也就是二維數組的第二行,*(aa + 1)相當于第二行的數組名,表示的是一維數組首元素的位址,即元素6的位址,再強制轉化為int*類型指派給ptr2,指向位址如圖所示。*(ptr2 - 1)就是向前移動一個int的大小,即指向元素5,解引用輸出5。
運作程式,輸出結果如下:
2.7.7 練習7
int main()
{
char* a[] = { "work","at","alibaba" };
char** p = a;
p++;
printf("%s\n", *p);//at
printf("%c\n", **p);//a
return 0;
}
分析:
字元型指針數組a中存放的是各字元串的位址,也就是個字元串首字母的位址,即字元w的位址,字元a的位址,字元a的位址。将數組a的首元素的位址存放到二級指針p中,即p中是字元w的位址的位址,p++後就是字元串at的首字元a的位址的位址,是以%s列印*p是at,%c列印**p是a。
運作程式,結果如下: