C语言-指针

1 运算

  • 加减法
    指针加减1:实际是加减指针所指变量的数据类型长度

    int a, *p_a = &a;
    char b, *p_b = &b;
    printf("%#X, %#X\n", p_a, p_b);   \\ 0XEFF9D8, 0XEFF9C3
    p_a++, p_b++;
    printf("%#X, %#X", p_a, p_b);      \\ 0XEFF9DC, 0XEFF9C4
    
    \\ p_a 是int,+1对应加4字节;p_b是char,+1对应加1字节
    

    在指针指向数组中的元素时有实际意义,可通过加减1指向上一个或下一个元素;其他情况下,变量间并不一定无缝存储,就无法通过加减1指向相邻变量

  • 比较
    地址相等意味着指向同一份数据

  • 其他运算均不成立

2 指针访问数组元素

数组中元素数据类型相同且连续排列,非常适合用指针指向数组内元素并访问

int a[4], *p = a;     // 数组名a可退化为指针常量,等价于&a[0],指向数组首地址

数组名退化为指针常量

越界问题:指针的权限范围和其指向的数组在内存中的范围是一致的,越界修改可能会有未知问题出现(如Stack around the variable ‘s’ was corrupted)

两种元素访问方式:

  • *(p+i) 或 *p++ 访问数组
    int a[3] = { 1,2,3 };
    int i, *p = a, len = sizeof(a) / sizeof(a[0]);
    for (i = 0; i < len; i++) {
    	printf("%d ", *p++);   
    }
    // 输出1 2 3
    
    *p++和*++p结果不同:*p++是先取p指向的值再自增;*++p是p先自增再取指向的值
  • p[i] 访问数组
    int a[] = { 1,2,3 }, *p = a;
    printf("%d, %d", p[0], a[0]);   // 1, 1
    
    p[i] 和 a[i] 等价

数组名和指向数组首地址的指针都可以用以上两种方法访问数组,但数组名为常量,只能指向数组首地址,无法指向其他元素

3 指针类型

3.1 字符指针:指向字符串首字符的指针

用字符指针间接储存字符串

char *str = "hello";  

字符指针储存的字符串不可变!

char *str = "hello";
str = "ok";    		 // 不报错:指向新的字符串首地址
str[0] = 'c'; 		 // 报错:修改字符串
  • 输出字符串
    指向字符串中某字符的字符指针可表示从此字符开始到\0(结束标志)的字符串

    char *str = "hello";
    printf("%s, %s", str, str+1);   \\ hello, ello
    
  • 字符访问方式:使用*(str+n)或str[n]
    规则和 2 指针访问数组元素 相同:

    printf("%c", *(str+1), str[1]);   \\ e, e
    
char * const str1 = "hello";
printf("%s, %c", str1, *str1);   // hello, h
char *str2 = str1; 				 //指针str1本身为常量,本来无法升权限赋值给变量str1,但是字符指针在给其他字符指针赋值时,表示的是字符串,所以这条语句不报错

3.2 数组指针:指向一维数组的指针(而非指向数组首元素)

datatype (*p)[len]  // p指向一个数组,数组的元素类型为datatype,长度为len,p的值为数组首地址

二维数组名可退化为数组指针常量:
二维数组名退化

int a[2][3] = { 1,2,3,4,5,6 }, (*p)[3] = a;   
// p+1指向下一行数组,*p+1指向下一列元素

3.3 结构体指针:指向结构体变量的指针

struct 结构体名 *p = value;
struct stu{
		int age;
		float score;
		char name[10];
} stu1 = {18, 90,"Tom"}, *p = &stu1;

获取结构体成员:

// 方法一
(*p).memberName   // *p的括号不能少,因为 , 的优先级高于()
// 方法二
p -> memberName   // -> 在C语言中唯一用途

结构体指针作为函数参数比copy整结构体高效

3.4 函数指针:指向函数的指针

一个函数占用一段连续内存空间,定义函数指针p指向内存首地址,即可通过*p或p调用函数

returntype(*p)(param list) = value  

returntype:函数返回值类型;param list:参数列表(可省略参数名,只给类型);第一个()不能省略,因为()优先级高于*;用函数作为value给函数指针赋值,函数会退化为相应类型的函数指针

int max(int a, int b) {
return a > b ? a : b;
}

int main() {
	int(*p)(int, int) = max;    
	printf("%d", (*p)(1, 2));   \\ 使用*p调用函数时注意加括号!
}

3.5 二级指针:指向指针的指针

datatype **p = value;

指针变量也会占用存储空间,可以用&获取地址赋给二级指针

int a = 100, *p1 = &a, **p2 = &p1;
printf("%d", **p2);  // 100

C语言不限制指针级数,增一级就在定义时多一个*,但超过二级的指针很少使用

3.6 空指针和void指针

空指针不指向任何对象,值为NULL,NULL是纯粹的0,地址0不运行程序访问,空指针作为函数调用失败的返回值;
void指针指向某个对象,但该对象不属于任何类型,可以和其他类型指针自动或强制转换

4 指针数组:指针作为元素的数组

datatype *p[len]  \\ p为数组,包含len个类型为 datatype * 的指针元素
int a = 1, b = 2, c = 3;
int *p[3] = {&a, &b, &c};
printf("%d", *p[0]);   \\ 1

5 指针变量作为函数参数

参数传递的本质是内存的copy!

两种情况下使用指针变量作为函数参数

  • 需要通过形参在函数内部的改变实时修改函数外部对应变量的值
    // main{} 内a, b换值
    void swap(int *p_a, int *p_b){
        int temp;
        temp = *a;
        *a = *b;
        *b = temp;
    }
    
  • 传递数组、字符串等复合数据类型
    数组形参退化为指针
    copy复合数据类型内所有值的效率低,所以C语言选择copy指针

6 指针作为函数返回值-指针函数

注意:函数在运行结束后会销毁内部定义的所有局部数据,所以返回的指针尽量不要指向这些数据,不一定能获取到!

// 函数返回的指针指向函数内部定义的n,但在main()中指针正常获取了n值
int *func() {
	int n = 100;
	return &n;
}

int main() {
	int *p = func(), n;
	n = *p;
	printf("%d", n);   // 100
	return 0;
}

“销毁”局部数据实际并没有清空它所占内存空间,而是程序清除了局部数据对这块内存的所有权,之后的代码可以随意使用这块内存。在func()运行后立刻使用指针,其实是可以获取n值的,但如果有其它函数被调用就会覆盖这块内存,得到的数据就失去了意义

// 指针无法正常获取n值
int *func() {
	int n = 100;
	return &n;
}

int main() {
	int *p = func(), n;
	printf("i love coding\n");  // 导致p指向的内存被占用
	n = *p;
	printf("%d", n);   // 14
	return 0;
}

7 数组名可 自动 退化成指针常量,但不等价

所谓退化即抛弃数组长度信息

一维数组名退化为指向数组首元素的指针:

int a[3] = {1, 2, 3};
printf("%d", *a);   \\  1   


二维数组名退化为指向数组首行的数组指针:

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

a的值是二维数组首行首元素的地址;*a是整个第一行数据(一维数组),(*a)[0]是首行首元素;在表达式中*a会退化成指针,指向首行首元素,类型为int *,所以**a也为首行首元素

printf("%d, %d, %d, %d, %d, %d\n", a, &a[0][0], sizeof(*a), (*a)[0], *a, (**a));  //输出15465044, 15465044, 12, 1, 15465044, 1
\\ *a[1]和(*a)[1]不同,前者实际为*(a[1])是第二行退化为指向首元素的指针后获取值,即a[1][0],后者是a[0][1]
  • 换行:a+1指向二维数组第二行

    printf("%d, %d, %d", a + 1, &a[1][0], sizeof(*(a + 1)));  // 15465056, 15465056, 12
    
  • 换列:*a+1指向第一行第二个元素,*(*a+1) 和 (*a)[1] 都表示第一行第二个元素

    printf("%d, %d, %d, %d\n", *a + 1, &a[0][1], *(*a + 1), (*a)[1]);  //   15465048, 15465048, 2, 2
    

退化规则:

  • 对数组下标的引用总是可以写成“一个指向数组起始地址的指针加上偏移量”
    str[i]等价于*(str+i)
  • 函数形参声明中,数组退化为指针(包括字符数组)
    C语言中只会以值拷贝的方式传递参数,copy整个数组效率低下,所以数组名自动退化为指针常量,从而copy指针
    一维数组 形参 退化为指针:
    void fun(int a[4]) {}
    
    void fun(int *a) {}
    
    二维数组 形参 退化数组指针:
    void fun(int a[4][5]) {}
    
    void fun(int (*a)[5]) {}
    

三种情况下不会退化:

  • 使用sizeof()函数时,数组名代表数组

    int a[3] = { 1, 2, 3 }, *p = a;
    printf("%d, %d", sizeof(a), sizeof(p));   \\ 12, 4
    
  • 对数组名取址操作时,数组名代表数组

    int a[3] = { 1, 2, 3 };
    printf("%d, %d", &a, &a[0]);   \\ 输出18085708, 18085708:&a 是对数组取址,获得数组首地址,而不是对指向数组数组首元素的指针取址
    
  • 使用字符串初始化数组时,数组名代表字符串

    char a[] = "abcde";
    printf("%d, %s", sizeof(a), a);  // 6, abcde
    
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值