字符指针和字符数组、多维指针和多维数组
指针的概念以及一维指针和一维数组相关的知识,我这里就不具体讲了,本文主要讲解字符指针和字符数组、多级指针和多级数组这些比较容易混淆的知识点。
-
指针运算
-
赋值运算
指针变量可以互相赋值,也可以赋值某个变量的地址,或者赋值一个具体的地址
int *px, *py, *pz, x = 10; //赋予某个变量的地址 px = &x; //相互赋值 py = px; //赋值具体的地址 pz = 0x4000;
-
加减运算
-
指针变量的自增自减运算。指针加 1 或减 1 运算,表示指针向前或向后移动一个单元(不同类型的指针,单元长度不同)。这个在数组中非常常用。
int arr[2] = {1, 2}; printf("%p %p %p %p\n", &arr[0], arr, &arr, arr+1, &arr+1); ----------------------------------------------- 输出:62FE40 62FE40 62FE40 62FE44 62FE48 //数值上:&arr[0]==arr==&arr //数组名arr表示数组首元素的首地址,即元素arr[0]的地址&arr[0] //&arr表示整个数组存储在内存中的首地址,数值上跟数组首元素的首地址相同,但表示的意义不同 //arr+1和&arr+1运算,指针所移动的单元长度是不同,前者移动一个int类型的长度,后者移动2个int长度(即数组的长度)
-
指针变量加上或减去一个整形数。和第一条类似,具体加几就是向前移动几个单元,减几就是向后移动几个单元。
-
-
关系运算
假设有指针变量 px、py。
-
px > py 表示 px 指向的存储地址是否大于 py 指向的地址
-
px == py 表示 px 和 py 是否指向同一个存储单元
-
px == 0 和 px != 0 表示 px 是否为空指针
//定义一个数组,数组中相邻元素地址间隔一个单元 int num[2] = {1, 3}; //将数组中第一个元素地址和第二个元素的地址赋值给 px、py int *px = &num[0], *py = &num[1]; int *pz = &num[0]; int *pn; //则 py > px if(py > px){ printf("py 指向的存储地址大于 px 所指向的存储地址"); } //pz 和 px 都指向 num[0] if(pz == px){ printf("px 和 pz 指向同一个地址"); } //pn 没有初始化 if(pn == NULL || pn == 0){ printf("pn 是一个空指针"); }
-
-
同类型指针相减
同类型指针相减,返回值是个整数,其值可用下面的公式计算:
(指针1的值 - 指针2的值)/指针所指变量占用的内存字节数
-
字符指针和字符数组
在 C 语言中本身没有提供字符串数据类型,但是可以通过字符数组和字符指针的方式存储字符串。
-
字符数组方式
定义一个数组存储字符串的元素。
char word[] = "Hello world!"; printf("%s", word);
-
字符指针方式
定义一个指向字符串的指针。指针方式操作字符串和数组操作字符串类似,可以把定义的指针看做是字符数组的数组名。
//除了定义一个字符数组外,还可以直接定义一个字符指针存储字符串 char *word = "Hello world!"; //此时可以做字符串的操作 //输出 printf("%s\n", word); //通过下标取字符 printf("%c\n", word[0]); //获取字符串长度,其中 strlen 是 string.h 库中的方法 printf("%d\n", strlen(sentence));
注:字符指针方式区别于字符数组方式,字符数组不能通过数组名自增操作,但是字符指针是指针,可以自增操作。
字符指针和字符数组如何应用呢?
我们知道数组是不能直接相互赋值的,下面使用字符指针将字符数组 sentence 中的内容复制到字符数组 word 中:
//定义字符数组 sentence 和 word,给 sentence 赋初值 char sentence[] = "Hello world!", word[13]; //word = sentence; 会报错! //定义字符指针,指向 word char *ch = word; int i; //循环赋值 for(i = 0; sentence[i] != '\0'; i++){ *(ch + i) = sentence[i]; } //在当 i 等于 sentence 的长度(sentence 的长度不包含'\0')时, //i 继续自增,此时判断 sentence[0] != '\0'不符合,跳出循环,则 i 比 sentence 长度大 1 *(ch + i) = '\0'; //输出字符串,因为 ch 指向 word,所以输出结果是一样的 printf("ch = %s, word = %s", ch, word);
-
多级指针
指针变量作为一个变量也有自己的存储地址,而指向指针变量的存储地址就被称为指针的指针,即二级指针。依次叠加,就形成了多级指针。我们先看看二级指针,它们关系如下:
其中 p 为一级指针,pp 为二级指针。
具体定义和操作:
//定义普通变量和指针变量 int *pi, i = 10; //定义二级指针变量 int **ppi; //给指针变量赋初值 pi = &i; //给二级指针变量赋初值 ppi = π //我们可以直接用二级指针做普通指针的操作 //获取 i 的内容 printf("i = %d", **ppi); //获取 i 的地址 printf("i 的地址为%d", *ppi);
-
多维数组
-
多维数组的地址
拿二维数组举例:
int nums[2][2] = { {1, 2}, {2, 3} };
分析:
-
将数组当成一种数据类型 x,那么二维数组就可以当成一个元素为 x 的一维数组。
-
先把数组nums看作一维数组,有两个元素,nums[0]和 nums[1];
-
将 nums[0]看做一个整体,作为一个名称可以用 a 替换。则 a [0]就是 nums[0] [0],其值为 1,以此类推。
我们知道数组名即为数组首地址,上面的二维数组有两个维度。首先我们把按照上面理解,那么 nums 就是一个数组,则nums 就作为这个数组的首地址。第二个维度还是取 nums[0],我们把 nums[0]作为一个名称,其中有两个元素。我们可以尝试以下语句:
printf("%d", nums[0]);
发现此语句的输出结果为一个指针,在实验过后,发现就是 nums[0] [0]的地址。即数组第一个元素的地址。
- 引申到三维数组
//假设已初始化,二维数组数据类型设为 x,一维数组数据类型设为 y int a[2][2][2] = { {{1, 2},{3, 4}}, {{5, 6},{7, 8}} }; //此数组首地址为该数组名称 printf("此数组首地址为%d\n", a); printf("a+1 = %d\n", a+1); printf("*(a+1) = %d\n", *(a+1)); //此数组可以看做存储了两个 x 类型元素的一维数组,则 nums[0] = x1 的地址为 printf("第二个维度的首地址为%p\n", a[0]); printf("a[0]+1 = %d\n", a[0]+1); printf("*(a[0]+1) = %d\n", *(a[0]+1)); //而 x1 可以看做存储了两个 y 类型元素的一维数组,则 y1 = x1[0] = nums[0][0] printf("第三个维度的首地址为%d\n", a[0][0]); printf("a[0][0]+1 = %d\n", a[0][0]+1); printf("*(a[0][0]+1) = %d\n", *(a[0][0]+1));
运行结果为:
于是三维数组实际存储形式为:
图中上面三层可以当作指针,第四层当作变量。a 、a [0] 、a[0] [0] 指向数组首元素的首地址,即&a[0] [0] [0],同理,它们只是数值上相等,性质是不同的;实际存储内容的为最内层维度,且为连续的。对于 a 来说,其个跨度为 4 个单元;对 a[0]、a[1] 来说,其跨度为 2 个单元;对 a[0] [0]、a[0] [1]、a[1] [0]、a[1] [1] 来说,跨度为 1 个单元,即越到内层,跨度越小。
-
-
多维数组的指针
我们知道指针数组元素可以指向一个数字,那么我们现在可以尝试用指针数组的每个元素指向一个数组:
//定义一个二维数组 int nums[2][2][2] = { {1, 2}, {2, 3} }; //此时 nums[0]、和 nums[1]各为一个数组, 我们可以用指针数组 p 操作一个二维数组 int *p[2] = {nums[0], nums[1]}; //p 为数组 p 的首地址,p[0] = nums[0] = *p,**p = nums[0][0] printf("nums[0][0] = %d\n", **p); //指针 + 整数形式,p+1 移动到 nums 的地址,*(p +1) = nums[1],则**(p + 1) = nums[1][0] printf("nums[1][0] = %d\n", **(p + 1)); //先*p = nums[0],再*p + 1 = &nums[0][1],最后获取内容*(*p + 1)即为 nums[0][1] printf("nums[0][1] = %d\n", *(*p + 1));
这里可能不能理解为什么*p + 1 = &nums[0] [1],而不是 nums[1]。p 获得的是一个一维数组,而 int 数组 + 1 的跨度只有 4 个字节,也就是一个单元。前面 p 是一维数组的指针,其跨度为一个数组。所以p + 1 = &nums[0] [1],而 p + 1 = nums[1]。
-
最后来道题加深一下印象
#include<stdio.h> int main() { char *str[]={"welcome","to","kaikeba","xinzhike"}; char **p=str; str[0] = *p+3; str[1] = *++p+2; str[2] = p[1]+3; str[3] = p[2]+(str[2]-str[1]); printf("%s\n", str[0]); printf("%s\n", str[1]); printf("%s\n", str[2]); printf("%s\n", str[3]); return 0; }
运行结果为:
分析一波:1、首先 str 是一个指针数组,数组的每个元素是字符串。str[0],str[1],str[2],str[3]分别指向每个字符串的首地址,其中str[0]比较特殊,即是第一个字符串的首地址,也是所以串(即整个数组)的首地址。
2、二级指针p, 指向的是一个指针变量,这个指针变量str就是str[0],所以p指针指向的是 “welcome” 这个字符串,*p == “welcome” 的真实地址,**p == “welcome”的第一个字符 ‘w’ ,以此类推则有:
**p[0] == str[0], p[1] == str[1], p[2] == str[2], *p[0] == ‘w’, *p[1] == ‘t’, p[2] == ‘k’, p[3]==‘x’
3、str[0]= * p + 3; 首先取*p,也就是“welcome”, 取出“welcome”时指向的是“welcome”的起始地址('w’的地址), 然后+3向后移动三个字符指向了“come”, 所以str[0] == "come"
4、str[1]= *++p+2; 首先++p, 因为p指向的是以 “welcome” 开头的后面所有字符串,自增后,p的指向由 str[0] 指向 str[1],然后 *++p == str[1],即"to" ,取出“to”时指向的是“to”的起始地址(‘t’的地址), 然后+2向后移动2个字符指向了"\0"(别忘了字符串最后还有一个空字符’\0’)。即str[1] == ""
5、str[2]=p[1]+3; p是从指向的str[1]开始的所有字符串,所以p[0]指向str[1],p[1]指向str[2],p[1]+3就是指向str[2]的第4个字符,所以 str[2]==“keba”.
6、str[3] = p[2] + (str[2] - str[1]); p[0]指向的是str[1], 则p[2] 指向 str[3],即"xinzhike" (注意str[1]和str[2] 已经变了哟) str[2] - str[1] = = 4 (str[2] 指向"kaikeba"的"keba",str[1] 指向"to"的"\0",因此他们之间差4个字符地址再/sizeof(char) == 4 ) ),所以在"xinzhike"首元素位置往后移动4个,所以str[3]=="hike"