没人比我懂C指针

1 前言

C的地位不必多说,开山鼻祖。常年在各种排名中霸榜,地位不可撼动。
红到发紫的Python也是C语言写的,Java也是C和C++写的,说白了,可以理解为Py是对C语言的更上一层的封装,说到这没有贬低Python的意思,只是说C语言真的很重要,如果你想了解Python的解释器,也可以叫做Python的虚拟机,你就必须会C,否则你就没有办法体会到Python的真谛,说白了,只能做一名掉包工程师罢了。
个人认为C语言之所有能够发展至今,经久不衰,跟指针是分不开的,还有我个人认为,C中最好的两个理念其一是指针,其二是typedef,我想用大白话跟大家讲一讲我对指针的理解和认识,希望能够帮助读者更好的理解指针。

2 指针概念

C语言是一种强类型语言,声明变量的时候要先定义这个变量的类型,按照不同的数据类型,C语言中分为整形变量,浮点型变量,布尔变量,结构体变量,枚举变量等等,在此不一一列举。
本文使用整形变量为例,说明C中怎么创建变量,并且为一个变量赋值,这个变量的值存在哪?这个变量的地址是多少?带着这些疑问,我先写一段代码,随后在画一张简图:

#include <stdio.h>


int main() {
	int a = 0;
	printf("a=%d\n", a);
	printf("a的地址=%p\n", &a);
	printf("a占用的存储空间=%d\n", sizeof(a));
	int* ptr;  // 表示指向地址存储的变量是整型
	ptr = &a;
	printf("ptr=%p\n", ptr);
	printf("ptr指向的地址的值=%d\n", *ptr);
	printf("ptr的地址=%p\n", &ptr);
	return 0;
}
/*
a=0
a的地址=00AFF794
a占用的存储空间=4
ptr=00AFF794
ptr指向的地址的值=0
ptr的地址=00AFF788
*/

在这里插入图片描述
其实看到代码输出的结果和我画的示意图,应该对指针有一个大致的理解:
指针就是一个变量的地址,指针变量是一个存储变量地址的变量,变量就是可以改变的量,如上例子中,我可以定义一个int b = 1,ptr=&b,将ptr变量中的值改成B的地址,再去使用指针取值的时候*ptr就等于1,而不等于0。说了这么一大段,总结一句话,指针也是一个变量,只不过存储的是地址而已。
定义指针变量时一定要对其进行初始化,否则容易出现指针指向随机不明地址,导致意想不到的bug。如果没有一个合适的值给一个新建的指针,那么可以将指针初始化为NULL,NULL表示空指针。

3 指针使用场景

好好的变量不用,为啥非要搞指针呢?这东西有什么好处呢?先来看下面两个例子,好多说指针都使用两个数据交换做为案例讲解,虽然很简单但是我觉得数据交换对于小白来说还是比较难。

  • 案例一:

使用函数修改一个变量的值,函数中使用普通整型变量,返回修改后的整型变量

#include <stdio.h>

int changeValue(int x, int value) {  // 这个函数的作用是把x的值修改成value,最后在返回x
	x = value;
	return x;
}

int main() {
	int a = 0;   
	printf("修改前a=%d\n", a);
	a = changeValue(a, 10);   // 使用a的值接收changevalue这个函数返回的整数值
	printf("修改后a=%d\n", a);
	return 0;
}

/*
	修改前a=0
	修改后a=10
*/

使用指针变量做为形参,修改一个变量值函数

#include <stdio.h>

void changeValue1(int* x, int value) {
	*x = value;
	return;
}

int main() {
	int a = 0;
	printf("修改前a=%d\n", a);
	changeValue1(&a, 11);
	printf("修改后a=%d\n", a);
	return 0;
}
/*
	修改前a=0
	修改后a=11
*/

看过案例一之后,对指针有没有一点感觉呢,C中函数是通过传值来给形参赋值的,如果你想修改一个变量的值,那么必须使用一个返回值接收修改后的值,否则这个变量不会被改变;
如果你不想使用这个返回值接收修改后的值,那么请把这个变量的地址做为实参传递给被调用的函数,就比如实例一的第二个程序,这样代码就会简洁很多。
相信我,也相信自己,当你慢慢理解指针之后,你会爱上C!
下面来看看一个稍微复杂一点的例子!

  • 案例二

修改一个指针变量的值,也就是说让指针变量指向一个其他的地址

int* changePtrVal(int* ptr, int* newPtr) { 
	ptr = newPtr;
	return ptr;
}

int main() {
	int a = 0;
	int* ptr = &a;
	int b = 1;
	printf("修改前ptr=%p\n", ptr);
	printf("修改前ptr指向地址存的值=%d\n", *ptr);
	ptr = changePtrVal(ptr, &b);
	printf("修改后ptr=%p\n", ptr);
	printf("修改前ptr指向地址存的值=%d\n", *ptr);
	return 0;
}
/*
修改前ptr=010FFD3C
修改前ptr指向地址存的值=0
修改后ptr=010FFD24
修改前ptr指向地址存的值=1
*/

由上面的程序可以看出来,通过changePtrVal函数将指针变量ptr的值修改成&b的地址。

使用双重指针来做参数,修改指针变量的值

void changePtrVal1(int **ptr, int* newptr) {
	*ptr = newptr;
	return;
}

int main() {
	int a = 0;
	int* ptr = &a;
	int b = 1;
	printf("修改前ptr=%p\n", ptr);
	printf("修改前ptr指向地址存的值=%d\n", *ptr);
	changePtrVal1(&ptr, &b);
	printf("修改后ptr=%p\n", ptr);
	printf("修改前ptr指向地址存的值=%d\n", *ptr);
	return 0;
}
/*
修改前ptr=00F5FB1C
修改前ptr指向地址存的值=0
修改后ptr=00F5FB04
修改前ptr指向地址存的值=1
*/

通过上面的例子应该能够理解指针做为形参的意义和作用,当你想修改一个值,就请传入这个值的地址,也就是指针,如果你想用函数返回值的方式修改,那么就传入一个值。

传值就是把一个变量的值做为实参传给形参
传址就是把一个变量的地址做为实参传给形参

4 指针和数组

一维数组

只要谈到指针数组是不可避免的话题。

int main() {
	int a[5] = { 0,1,2,3,4 };
	printf("a = % p\n", a);
	printf("& a[0] = % p\n", &a[0]);
	printf("& a[1] = % p\n", &a[1]);
	printf("& a[2] = % p\n", &a[2]);
	printf("& a[3] = % p\n", &a[3]);
	printf("& a[4] = % p\n", &a[4]);
	return 0;
}
/*
a = 00D3F834
& a[0] = 00D3F834
& a[1] = 00D3F838
& a[2] = 00D3F83C
& a[3] = 00D3F840
& a[4] = 00D3F844
*/

在这里插入图片描述

数据名a就是指向数组首地址的指针

int main() {
	int a[5] = { 0,1,2,3,4 };
	printf("*a = % d\n", *a);
	printf("*(a+1) = % d\n", *(a+1));
	printf("*(a+2) = % d\n", *(a+2));
	printf("*(a+3) = % d\n", *(a+3));
	printf("*(a+4) = % d\n", *(a+4));
	return 0;
}
/*
*a =  0
*(a+1) =  1
*(a+2) =  2
*(a+3) =  3
*(a+4) =  4
*/

通过上面这两个例子,我希望读者能够理解下面的公式:
a[i] = *(a+i)

数组名就是数组首元素的地址,a+1=a[1]的地址,指针变量可以进行加减运算,前提是要知道指针所指向的地址所占的字节数,看下面的例子,理解指针的加减运算。

int main() {
	int a[5] = { 0,1,2,3,4 };
	char c[5] = {'H','e','l','l','o'};

	printf("a = %p\n", a);
	printf("a+1 = %p\n", a+1);

	printf("c = %p\n", c);
	printf("c+1 = %p\n", c + 1);
	return 0;
}

/*
a = 012FFD38
a+1 = 012FFD3C
c = 012FFD28
c+1 = 012FFD29
*/

指针的加减运算就是指针的跨度问题,不同类型的指针跨度不同,整型+1,地址会加四个字节,字符型+1,地址会加一个字节。

二维数组

int main() {
	int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	printf("a = %p\n", a);
	printf("a+1 = %p\n", a+1);
	return 0;
}
/*
a = 00B3FD00
a+1 = 00B3FD10
*/

a~a+1 垮了16个字节,是因为四个int型总共占用16个字节

int main() {
	int a[3][4] = { {1,2,3,4},{5,6,7,8},{9,10,11,12} };
	printf("*(*(a)) = %d\n", *(*(a)));
	printf("*(*(a+1)) = %d\n",*(*(a+1)));
	return 0;
}

/*
*(*(a)) = 1
*(*(a+1)) = 5
*/

在这里插入图片描述
通过上面的代码和图片,读者应该对下面的式子有一定的理解

a[0][0] = *(*(a+0))

在定义一维数组的时候,可以直接写int a[] = {1,2,3}…
在定义二维数组的时候,可以直接写int a[][3] = {{1,2,3},{4,5,6}}
但是当你写a[][]的时候编译器就会给你报出错误
在这里插入图片描述
这个可以这样理解,对于数组这种连续的数据存储结构,是需要使用指针进行数据读取的,数组名就是首地址,对于一维度数组来说,首地址+1,就是数组名[1]的地址,只要定义了数组类型,对于一维度数组来说就知道地址间隔。对于二维数组b[3][4]来说,b+1是b[1]的地址,如果不写a[3][4],就不知道指针+1是多少,这段可能比较抽象,我不知道自己的表达是不是清晰。

字符数组

字符数组是一种特殊的数组,C中没有字符串数据类型,如果想定义一个字符串,有两种方式:第一种是字符数组,第二种是字符指针。

字符数组

int main() {
	char ch[10] = { 'H','e','l','l','o'};

	printf("ch = %s\n", ch);

	return 0;
}

字符指针

int main() {
	char* str2;
	str2 = "C Language";
	printf("str2 = %s\n", str2);
	return 0;
}

C语言存储字符数组时,会使用‘/0’做为一个字符串的结束符。

5 指针数组和数组指针

这两个概念很多人傻傻分不清

指针数组

指针数组是一个数组,数组的元素是指针。
*arrPtr[3] 下标运算符和解引用运算符具有相同的优先级,自右向左结合

int main() {
	int a = 1, b = 2, c = 3;
	int* arrPtr[3] = { &a,&b,&c };
	for (int i = 0; i < 3; i++) {
		printf("arrPtr[%d] = %p存储的值=%d\n", i, arrPtr[i],*arrPtr[i]);
	}
	return 0;
}

/*
arrPtr[0] = 012FFD1C存储的值=1
arrPtr[1] = 012FFD10存储的值=2
arrPtr[2] = 012FFD04存储的值=3
*/

这个例子其实没有实际意义,再举一个例子

int main() {
	char ch1[] = "Hello";
	char ch2[] = "World";
	char ch3[] = "Fei";
	char* chPtr[3] = { ch1,ch2,ch3 };
	for (int i = 0; i < 3; i++) {
		printf("chPtr[%d] = %p存储的值=%s\n", i, chPtr[i],chPtr[i]);
	}
	return 0;
}
/*
chPtr[0] = 012FFB38存储的值=Hello
chPtr[1] = 012FFB28存储的值=World
chPtr[2] = 012FFB1C存储的值=Fei
*/

数组指针

(*ptr)[3] ptr是一个指针,指向的是一个数组

int main() {
	int a[3] = { 1,2,3 };
	int(* ptr)[3] = &a;
	for (int i = 0; i < 3; i++) {
		printf("%d\n", (*ptr)[i]);
	}
	return 0;
}
/*
	1
	2
	3
*/

本篇文章没有复杂的案例,旨在让小白或者对C指针迷惑的朋友能够清晰指针的含义。

  • 71
    点赞
  • 297
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 24
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

kobe_OKOK_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值