C语言指针(1)

目录

1.内存和编址

​编辑

2.&(取地址)与*(解引用)

2.1—& 

2.2—*

 2.3指针变量的类型与大小

3.指针类型的意义

3.1指针解引用

3.2对指针变量++或--整数

3.3特殊的void*型指针

4.const修饰指针变量

 5.指针的与运算

5.1指针加减整数

5.2指针减指针

6.野指针

7.数组指针变量

7.1数组指针变量的初始化

8.数组名与&数组名

9.数组指针变量加减整数

10.数组传参本质

 10.1 一维数组传参

10.2 二维数组传参


指针可以说是c语言中非常核心的一个特征了,要了解它,我们可以先从内存和编址下手。

1.内存和编址

我们都知道手机,电脑等许许多多的电子产品都有内存,内存中存放着我们保存的信息,要知道内存是一块很大的空间。

94957c25bbe34a20a4dde9cc1e78244e.png

所以为了很好的管理起这些空间,我们的电脑都是通过许多硬件来一起完成的。不过今天我们只关注:地址总线

 一般来说多少位的机器就有多少根地址线,像32位的机器就有32根地址线,一根线可以通过改变有无脉冲来表示1或0,1根地址线就可以表示俩种含义,那么32根地址线就有2^32种含义,即每一种含义代表一个地址,内存就通过地址线来给cup下达命令,从而精确的找到内存中的任意空间。

ac957136ac254893ae4fb4eb4742b730.png

看上面一段代码,开始调试,添加监视内存我们可以很清楚的看见一行行数字,这些就是内存中的地址了。

2.&(取地址)与*(解引用)

2.1—& 

在上面的代码中我们可以很明显的看见我们创建的变量a的地址是:0X0000004122AFFC04。

想把a的地址给取出来,就要用到单目操作符:&(取地址)。

而它取出的地址都是要存放在指针变量中的。

2.2—*

指针变量:指针变量也是变量,只是指针变量是专门用来存放地址的。

当我们的地址存放到指针变量中我们想要找到指针变量(地址所指向)的数据时就要用到

单目操作符:  *(解引用操作符)。通过解引用对指针变量进行操作就可以得到地址所指向的数据

 2.3指针变量的类型与大小

类型32位机器下64位机器下
char*4个字节8个字节
int*int*4个字节8个字节

从上面列举的2种指针变量中我们得知,在同一平台下指针变量的大小都一样。

3.指针类型的意义

既然每种类型的指针大小都一样那为什么还要有这么多指针类型呢?主要有下面俩种原因:

3.1指针解引用

bdd8c0f7ed994684a8766877ab736999.png

当我们创建一个指针变量int*p 和char *p它们指向的地址一样。

对char*p 与int *p进行解引用:*(p)解引用的不同之处在于char* p解引用只能访问1个字节的空间

而int* p却能访问4个字节的空间。

3.2对指针变量++或--整数

对指针变量进行整数的加减,实际是控制指针向前或向后移动的距离(字节):

指针指向的数据类型所占字节的大小

例如:

char* p指针进行+1,char* p指向的数据类型大小应为1个字节,所以char* p+1,跳过了一个字节

int* p指针进行+1,int* p指向的数据类型大小为4个字节,所以int* p+1,跳过了四个字节

18b22f8fe9264134bdea30ebb452b193.png

注:拿int *p指向一个char数据也可以,但是int* p的本质是一个指向int数据类型的指针 ,所以对它进行加减,应该拿int的字节大小来算指针的移动距离。指针的移动距离还是得看指针的类型是什么

3.3特殊的void*型指针

void*型指针在相同的平台下的大小也一样,但是它特殊的点在于并不可以对它进行整数的加减

与解引用因为void*是无具体类型的指针,如果你对它进行这俩个操作,系统不知道操作几个字节

因此不可以直接对void*的指针进行上诉操作。

4.const修饰指针变量

const修饰指针左侧:const int* p 或int const * p可修改指针指向的对象,不可修改指针指向的内容
const修饰指针右侧:int *const p 可修改指针指向的内容,不可修改指针指向的对象
const修饰指针左右俩测:const int* const p指针指向的内容,指针指向的对象均不能被修改

5.数组名与   &数组名

首先我们要认识:数组首元素地址与数组地址。

fe28451510a54cfda35aa350c4a5eb64.png

 sizeof(数组名),sizeof中单独放数组名,这里的数组名表示整个数组,计算的是整个数组的大小
单位是字节
 &数组名,这里的数组名表示整个数组,取出的是整个数组的地址(整个数组的地址和数组首元素
的地址是有区别的)
除此之外,任何地方使用数组名,数组名都表示首元素的地址。

 6.指针的运算

6.1指针加减整数

前面已简单介绍,这边在进行代码的加入,进行理解。

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

对指针变量a进行赋值,地址为数组arr首元素的地址,一开始,指针指向对象的内容是1。

让指针+1后,指针往后走4个字节,指向对象变成了数组的第二个元素。

在让a+1指向的就是第三个元素,依次类推,arr数组里的元素都可以遍历。

6.2指针减指针

先说结论:指针减指针的绝对值是俩个指针之间的元素个数。

int main() {
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p1 = &arr[0];
	int* p2 = &arr[9];
	printf("%d", p2 - p1);
	return 0;
}

2ba100ee4a0042778d67cd284d5dcaf8.png

以上代码和的输出结果是9,与我们研究的p1—p2之间的元素为9一样。

7.野指针的形成和预防

野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)指针变量在定义时如果未初始化,其值是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,所以结果是不可知的。

野指针形成的原因:

1.指针未初始化

int main() {
	int* p;
	printf("%d", *p);
	return 0;
}

4ab5765dc5e34d39b1463c41148df57f.png

 由于所以的指针变量刚创建出来时都不会自动赋值,所以它的值是随机的,如果使用了这样的指针系统就会报错。

2. 指针指向的空间释放

指针所指的内存被释放掉,但并没有把指针本身干掉。此时指针指向的就是“垃圾”内存。例如:

int test() {
	return 0;
}
int main() {
	int(*p)() = test;
	printf("%d", p);
	return 0;
}

8f10dc8fa791498d8e755b5d3e1538c8.png 当指针指向释放空间时它就是一个野指针。

3. 指针操作超越变量作用域

int main()
{
	int arr[5] = { 0 };
	int* p = &arr[0];
	int i = 0;
	for (i = 0; i <= 6; i++)
	{
		*(p++) = i; //当指针指向的范围超出数组arr的范围时,p就是野指针
	}
	return 0;
}

野指针的预防 

养成以下良好习惯,可以很有效的规避野指针:

1.创建指针变量时一定要初始化,指向有效变量的地址,或者指向NULL。

2.当指针p指向的内存空间释放时,设置指针p的值为NULL。

3.使用指针时判断是否合法,通常使用if语句判断是否为NULL

8.数组指针变量

数组指针变量顾名思义就是指向数组的指针变量,通过前面的介绍我们已经知道有:int* char*

型的指针变量,数值指针变量长这样:数组元素类型(*数组名)[数组元素个数]。

举例:int (*p) [10] 这个数组指针变量指向一个元素为int型元素个数为10个的数组,数组名为p。

为什么呢?

* 先与p结合,说明它是一个指针,指向类型为int [ 10 ]的数组

注意!这里的括号不能少,因为 [  ] 的优先级比 * 高,没有括号,  [  ]就会先和指针名结合,那么它就不是一个指针了,得先让 * 和指针名结合,才能说明它是一个指针。

8.1数组指针变量的初始化

int main()
{
	int arr[5] = { 0 };
	int(*p)[5] = &arr;
	return 0;
}

 将数组的地址使用&符号取出,赋值给数组指针变量即可。

9.数组指针变量加减整数

前面已经说过,对指针变量加减整数就是让指针越过一定的距离(字节)。

而越过的距离(字节)取决于指针的类型

例如 int(*p1)[5]

*先与p1结合,说以p1是一个指针,指向一个类型为int [ 5 ] 的数组

所以当我们对int(*p1)[5]指针变量进行加减的时候,越过的是类型为int [ 5 ] 的数组的大小

7d5d5a0daf51484c80a715a349bb7ea9.png

10.数组传参本质

 10.1 一维数组传参

在此之前,你是否有个疑问,为什么数组元素的计算都要在数组外部?

void test(int arr[5],int sz1) {
	int sz2 = sizeof(arr) / sizeof(arr[0]);
	printf("%d\n", sz1);         //打印5
	printf("%d\n", sz2);         //打印1
}
int main() {
	int arr[5] = { 1,2,3,4,5 };
	int sz1 = sizeof(arr) / sizeof(arr[0]);
	test(arr, sz1);
	return 0;
}

 046b88702e94424aafb28d59d7107cdb.png

前面说过只有在sizeof()里的数组名和&数组名才能拿到一整个数组地址,其他的数组名都是首元素地址,传参过去的arr一样也是首元素地址。

                                                         使用指针为形参

void test(int* arr)//参数写成指针形式,指针变量为数组首元素的类型
{
	printf("%d\n", *arr);
}
int main()
{
	int arr[5] = { 1,2,3,4,5};
	test(arr);
	return 0;
}

总结:一维数组传参,形参的部分既可以写成数组形式,也可以写成指针形式。 

10.2 二维数组传参

void test(int arr[3][4]) {

}
int main() {
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6,} };
	test(arr);
	return 0;
}

创建一个arr数组,研究arr在内存中的存放情况

d0c55e9f99964d6da66360772c4add84.png

前面我们也说过,传参传过去的数组名是首元素的地址,其实这句话放在二维数组上也没有错。

arr(二维数组)传过去的就是,arr的首元素。 

294dda6a0b6d458d86945dfc6573dd7f.png

81cf4ddaac8a4d9da02e465d5a02e255.png

                                                                使用指针为新参

void test(int (*arr)[4]) //参数写成指针形式,指针变量为数组首元素的类型
{

}
int main() {
	int arr[3][4] = { {1,2,3,4},{2,3,4,5},{3,4,5,6,} };
	test(arr);
	return 0;
}

总结:二维数组传参,形参的部分既可以写成数组形式,也可以写成指针形式。 

  • 25
    点赞
  • 32
    收藏
    觉得还不错? 一键收藏
  • 6
    评论
评论 6
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值