如果对指针不是那么熟悉,我这里有几篇指针相关入门,不知道能不能帮助到你
看完后,检测一下这两段代码是否能透彻理解
(1)
#include<stdio.h> int main() { int a=100; int *p; p=&a; printf("%d",*p); } 其输出为100
(2)
int a = 100; int *p1 = &a; // 一级指针,指向普通变量 int **p2 = &p1; // 二级指针,指向一级指针 int ***p3 = &p2; // 三级指针,指向二级指针
如果一个指针变量 p1 存储的地址,是另一个普通变量 a 的地址,那么称 p1 为一级指针
如果一个指针变量 p2 存储的地址,是指针变量 p1 的地址,那么称 p2 为二级指针
如果一个指针变量 p3 存储的地址,是指针变量 p2 的地址,那么称 p3 为三级指针
以此类推,p2、p3等指针被称为多级指针
一.数组与指针的表示
int a[3]; // 此处,a 代表整个数组
printf("%d\n", sizeof(a)); // 此处,a 代表整个数组
printf("%p\n", &a); // 此处,a 代表整个数组,此处为整个数组的地址
int *p = a; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
p = a + 1; // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
function(a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
scanf("%d\n", a); // 此处,a 代表首元素 a[0] 的地址,等价于 &a[0]
在大多情况下,指针都是指向数组的首元素,可以这么说,数组运算实际上就是指针运算。
补充:
字符串实质上是一个匿名数组,应用和数组很类似:
printf("%d\n", sizeof("abcd")); // 此处 "abcd" 代表整个数组
printf("%p\n", &"abcd"); // 此处 "abcd" 代表整个数组
printf("%c\n", "abcd"[1]); // 此处 "abcd" 代表匿名数组的首元素地址
char *p1 = "abcd"; // 此处 "abcd" 代表匿名数组的首元素地址
char *p2 = "abcd" + 1; // 此处 "abcd" 代表匿名数组的首元素地址
a[i] = 100; 等价于 *(a+i) = 100;
所以易得,a[i]=100,也与下面几项等价
a[i] = 100;
*(a+i) = 100;
*(i+a) = 100;
i[a] = 100;
例:
用三种等价的方法表示a[0][0]
&a[0][0]
//a[0]是首元素a[0][0]的地址
a[0]
//a[0]=*(a+0)=*(a)=*a
*a
二. 数组与指针语法
数组语法剖析
任意的数组,不管有多复杂,其定义都由两部分组成。
第1部分:说明元素的类型,可以是任意的类型(除了函数)
第1部分:说明数组名和元素个数
int a[4]; // 第2部分:a[4]; 第1部分:int
int b[3][4]; // 第2部分:b[3]; 第1部分:int [4]
int c[2][3][4]; // 第2部分:c[2]; 第1部分:int [3][4]
int *d[6]; // 第2部分:d[6]; 第1部分:int *
int (*e[7])(int, float); // 第2部分:e[7]; 第1部分:int (*)(int, float)
指针语法剖析
任意的指针,不管有多复杂,其定义都由两部分组成。
第1部分:指针所指向的数据类型,可以是任意的类型
第2部分:指针的名字
char (*p1); // 第2部分:*p1; 第1部分:char;
char *(*p2); // 第2部分:*p2; 第1部分:char *;
char **(*p3); // 第2部分:*p3; 第1部分:char **;
char (*p4)[3]; // 第2部分:*p4; 第1部分:char [3];
char (*p5)(int, float); // 第2部分:*p5; 第1部分:char (int, float);
来一个较为复杂的
int (*parr3[10])[5];//parr3是一个数组,该数组有10个元素,每个元素是一个数组指针,该数组指针指向的数组有5个元素,每个元素是int类型
分析一下
parr3[10]提出来,是一个数组,该数组有是个元素,剩下:
int(*)[5]:代表的是一个数组指针,数组指针指向的数组有5个元素,每个元素int型
例:
定义一个整型数 i
定义一个指向整型数的指针 p
定义一个指向整型指针的指针 k
定义一个有 3 个整型数的数组 a
定义一个有 3 个整型指针的数组 b
定义一个指向有 3 个整型元素的数组的指针 q
定义一个指向函数的指针 r,该函数有一个整型参数并返回一个整型
参考代码
1. int i;
2. int *p;
3. int **k;
4. int a[3];
5. int *b[3];
6. int (*q)[3];
7. int (*r)(int);
三.指针运算
指针p自增
#include <stdio.h> int main(void) { int a[] = {1, 2, 3, 4}; int i, *p; for(i=0, p=a; i<4; i++, p++) { printf("%d %d\n", a[i], *p); } return 0; }
注:这里p=a,表示让指针指向数组a的首元素1,再p++,使指针p不断自增
得到输出结果为
1 1
2 2
3 3
4 4
*p和*(p+1)
#include<stdio.h> int main() { int *p; int a[2][2] = {{1, 0}, {2, 3}}; p= a[0]; printf("%d, %d", *p, *(p+1)); return 0; }
p指向数组的首元素a[0][0],所以*p=a[0][0]的值,*(p+1)=p[0][1]
所以输出为:1,0
重要知识点,先来看代码
将字符串中的小写字母转换为大写字母
#include <stdio.h> #include <limits.h> #include <ctype.h> void upper_case(char str[]) { int step = 'a' - 'A'; for(int i = 0; i<sizeof(str)/sizeof(str[0]); i++) { if(islower(str[i])) str[i] -= step; } } int main(void) { char str[] = "abcdefghijklnmopqrstuvwxyz"; printf("原数组:%s\n", str); upper_case(str); printf("转换后:%s\n", str); }
这段代码有一个错误,在函数upper_case中,sizeof(str)/sizeof(str[0])的结果并不是数组长度,而是指针的大小。
这是因为数组在除了定义和sizeof语句之外,均会被视为指向其首元素的指针,因此在上述代码中, upper_case(str) 中的str是一个指针,而非数组,等价于:
upper_case(&str[0]);
所以str永远都是指针,而非数组,所以sizeof(str)无法计算得数组大小
在这里,可以将数组长度作为额外的参数传递给函数upper_case,或者使用一个特殊的字符(如'\0')作为数组的结束标志,然后在循环中检查该标志来确定数组的长度。
#include <stdio.h> #include <limits.h> #include <ctype.h> void upper_case(char str[]) { int step = 'a' - 'A'; for(int i = 0; str[i] != '\0'; i++) { if(islower(str[i])) str[i] -= step; } } int main(void) { char str[] = "abcdefghijklnmopqrstuvwxyz"; printf("原数组:%s\n", str); upper_case(str); printf("转换后:%s\n", str); return 0; }
四.结构体中指针的应用
struct node { /* 结构体的其他成员 */ // 成员1 // 成员2 // ... ... int len; char data[0]; }; // 给结构体额外分配 10 个字节的内存。 struct node *p = malloc(sizeof(struct node) + 10); p->len = 10; // 额外分配的内存可以通过 data 来使用 p->data[0] ~ p->data[9]
五.进阶练习
(1)编写一个程序,测试当前平台的字节序
#include <stdio.h> int main(void) { // 定义一个4字节的整型数据 int a = 0x12345678; // 定义一个char型指针指向最低地址 char *p = &a; // 将最低字节数据打印出来 // 如果是0x78,那就代表最低地址存储了低有效位,是小端序 // 如果是0x12,那就代表最低地址存储了高有效位,是大端序 printf("%#x\n", *p); }
(2)(数组下标运算、指针运算、内存操作)
注:这里的sizeof(long)是四个字节
#include <stdio.h> int main(void) { long a[4] = {1, 2, 3, 4}; long *p1=(long *)(&a+1); long *p2=(long *)((long)a+1); printf("%lx\n", p1[-1]); printf("%lx\n", *p2); return 0; }
解析:
首先,&a是整个数组的地址,因此&a+1实际上是向后偏移了16个字节,因此 p1 指向了数组边界。(引用博主@赵日天他大哥的图片)
然后输出 p1[-1] 等价于 *(p1-1),因为 p1 是 long 型指针,因此 p1-1 就向前偏移 sizeof(long) 个字节,也就是指向了 a[3],即4。
其次,(long)a+1 是一个纯整数运算(因为a被强转为long了),因此 long型指针p2 就指向了long型数据 a[0] 的第二个字节
最后打印 *p2 时,由于 p2 是一个 long 型指针,系统会从 a[0] 的第二个字节开始,取出 sizeof(long) 个字节出来作为 long 型数据来解释,因此最后输出的结果是 a[0] 的高位三字节和 a[1] 低位一字节的数据
a[0]的第二个字节开始,往后数四个字节,就是a[0] 的高位三字节和 a[1] 低位一字节的数据
(3)声明一个二维 int 型数组 a,再声明另一个一维数组指针数组 b,使该数组 b 的每一个指针分别指向二维数组 a 中的每一个元素(即每一个一维数组),然后利用数组 b 计算数组 a 的和。
注:“指针数组”指的是装了很多指针的数组,“数组指针”指的是指向数组的指针,“数组指针数组”指的是装了很多数组指针的数组。
#include <stdio.h> int main(void) { int a[2][3] = {{1, 2, 3}, {4, 5, 6}}; int (*b[2])[3]; 1. *b[2]//数组b有两个元素,每个元素都是一个指针,与int (*b)[2]进行区别,这里是包含两个整形元素的指针 2. int (*b[2])[3]//该数组是包含两个指针的数组,每个指针指向3个int类型元素 b[0] = &a[0];//第一个指针指向a[0]的地址 b[1] = &a[1];//第二个指针指向a[1]的地址 int i, j, sum=0;//初始化sum for(i=0; i<2; ++i) { for(j=0; j<3; ++j) { sum += (*b[i])[j];//作和 } } printf("sum: %d\n", sum); return 0;
}
(4)编写一个程序,去掉给定字符串中重复的字符。
#include <stdio.h> #include <string.h> void strip(char *str) { if(str == NULL) return; int i, j; char *p = str; // 将所有重复的字符标记为-1 for(i=1; str[i] != '\0'; i++) { for(j=0; j<i; j++) { if(p[j] == str[i]) { str[i] = -1; break; } } } // 找到第一个-1的位置 redun // 找到紧随 redun 之后的正常字符的位置 common int redun = 0, common = 0; for(i=1; str[i] != '\0'; i++) { if(str[i] != REDUNDANT) continue; redun = i; while(str[i] == REDUNDANT) i++; common = i; break; } // 若没有重复的字符,则返回 if(redun == 0) return; // 将所有正常字符往前移 while(str[common] != '\0') { if(str[common] == -1) { common++; continue; } str[redun++] = str[common]; str[common++] = -1; } str[redun] = '\0'; } int main(void) { // 输入待处理字符串 char buf[64]; fgets(buf, 64, stdin); //对于fgets的应用可以看 //http://t.csdn.cn/mDMBb strip(buf);//删除buf字符串 printf("%s", buf); return 0; }
strip示例