指针的理解part1

目录

1.内存和地址 

1.1内存

1.2地址

2指针变量和地址

2.1地址操作符 &

2.2指针变量和解引用操作符(*)

2.2.1指针变量 

 2.3指针变量的大小

3指针变量类型

3.1指针解引用

3.2指针 +-整数

4.const指针 

4.1const修饰变量

4.2const修饰指针变量

5.指针运算

5.1指针+-整数

5.2指针-指针

5.3指针的关系运算

5.3.1数组名

5.3.2关系运算

6.野指针

6.1指针成因

6.1.1指针未初始化

6.1.2指针越界访问

6.1.3指针指向的空间释放

6.2规避野指针

6.2.1指针初始化

6.2.2避免指针越界

6.2.3指针变量不使用时,及时置NULL,指针使用之前检查有效性

6.2.4避免返回局部变量的地址

7assert断言

 8.指针的使用和传址调用

8.1传址调用


1.内存和地址 

1.1内存

cpu读取数据在内存中读取,读取时要根据内存单元的地址来读取,一个内存单元的大小取1个字节。一个位存储一个0/1的二进制数,1PB=1024TB=1024*1024GB=1024*1024*1024MB......=1024*1024.....bit

1024进制,bit>>byte>>KB>>MB>>GB>>TB>>PB

内存单元的地址就是给内存单元的编号,在c语言中也叫做指针。

1.2地址

内存的地址是硬件设计的,是直接定义的。

计算机中有地址总线和数据总线等。cpu要向想读取数据,要将地址通过地址总线传给内存,然后内存再将地址对应的字节里的数据再通过数据总线传递给cpu。

2指针变量和地址

2.1地址操作符 &

c语言中创建变量/数组就是向内存申请空间。假如是char类型的变量,申请了4个字节的空间,每个字节都会有相应的地址,可以通过调试查看

例如创建了b变量,那么如果要取/调用b的地址,可以用&符号,比如&b表示的就是b变量对应的地址,注意,一个变量占据的空间不一定只有1个字节,如果有多个字节,取出或调用的是地址编号最小的。如果想知道别的地址,在知道了类型后,就可以推演了。

2.2指针变量和解引用操作符(*)

2.2.1指针变量 

地址也可以提前存储,存储的地方就是指针变量

指针变量也是变量,但存储的内容都被计算机理解为地址

例如

int *ba=&u;

2.2.2指针类型

int *ba=&u;
//*说明ba是指针变量
//int 说明ba这个指针变量存储的地址对应的数据,是int类型

2.2.3解引用操作符

int a=1;
int *ba=&a;
*ba=12;
//*ba=12就是一种解引用操作符的用法
//*ba存储的是a的地址,那么这里就是通过地址指向了a变量,那么后面的赋值操作符意思
//就是将a变量的值变成了12。

 2.3指针变量的大小

假设是32位机器,那么一个地址就是4个字节,32位描述存储。

相应的,指针变量也得是4个字节,用来存储地址

如果是64位,就都变成8字节

注意,指针变量的大小跟地址的大小有关,地址的大小跟机器的字长有关。同一个机器下,大小都是一样的

3指针变量类型

3.1指针解引用

指针变量类型决定了能访问的地址字节数

比如,int类型能访问32位机器下的地址所有字节,char类型只能1个字节

int a=88888888;
char *pr=(char *)&a;
*pr=0;
//这里就只能访问a变量的地址的一个字节
//因此只能改变这一个字节地址对应的数据

3.2指针 +-整数

#include<stdio.h>
int main()
{
	int a = 10;
	char* pr = (char*)&a;
	int* p1 = &a;
	printf("%p\n",&a );
	printf("%p\n",pr );
	printf("%p\n",pr+1 );
	printf("%p\n",p1 );
	printf("%p\n", p1+1);
	return 0; 
}

 第一行打印的是a变量占据的字节中,地址最小的

第2行打印的是a变量占据的字节中,地址最小的,但要注意的是,比如char类型只能访问一个字节,那么会从地址最小的那个开始访问

第3行打印的char类型的指针变量+1,但这里的+1具体跳过的字节数要看指针变量的类型,比如char类型占一个字节,那么跳过的字节数也是1个字节,如果是int类型,那么就像第5行打印的一样,跳过的是4个字节

第4行打印的是int类型的指针变量,本质上跟第一行打印是一样的,p1存储的就是a变量的地址,而&a就是a变量的地址。

4.const指针 

4.1const修饰变量

const可以让变量不能被修改

const int a=10;
a=20;//这里就不行了
&a=20;//这里就可以了,可以通过指针进行修改

4.2const修饰指针变量

由图可见,test2-4都有问题

1.const放在*左边,指针指向的变量不可以修改,但指针变量本身存储的地址可以修改

2.const放在*右边,指针指向的变量可以修改,但指针变量本身存储的地址不可以修改

3.const放在*左右两边,指针指向的变量不可以修改,指针变量本身存储的地址不可以修改

 4.不放const,指针指向的变量可以修改,指针变量本身存储的地址也可以修改

5.指针运算

指针运算:指针+-整数      指针-指针        指针的关系运算

5.1指针+-整数

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = &arr[0];
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	for (i = 0; i < sz; i++)
	{
		printf("%d", *(p + i));
	}
	return 0;
}
p初始化值是数组的第一个元素的最小地址
p+i是为了读取不同元素,因为p是int类型的指针变量,所以每次+1都是跳过4个字节
也就是下一个元素的最小地址

5.2指针-指针

 根据监视,我们看到,*p的地址最开始a字符的地址,之后直到\0的地址,而*s的地址一直都是a的地址。最后,p-s,返回的就是字符串"abc"中,‘\0’字符的地址减去'a'字符的地址,结果就是3,也就是一共3个元素,这3个元素分别占着1个字节,分别放着abc,3个字符。如果是int类型也不影响,因为指针的加减范围,是根据指针类型决定的,尽管int类型的指针是4个字节,但是加减时,每+1也是跳过了3个字节,所以指针-指针时,也是如此。

简而言之,指针-指针就是求两个指针范围内的字符或数字的个数。

5.3指针的关系运算

5.3.1数组名

数组不是指针,但大多数都是被编译器隐式转换成指向数组首元素的指针来处理。

但要注意的是,如果用sizeof运算符,则得到的是数组所占内存大小

如果直接用&数组名跟数组名的值都是一样的,但如果运用到计算,是不一样的

数组名+1,就是转到下一个元素,比如int类型的数组,就是跳过4个字节。如果是&数组名+1,就是跳过整个数组。

平时见到的,a【3】是会自动转换成*(a+3);

5.3.2关系运算

#include<stdio.h>
int main()
{
	int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
	int* p = arr;
	int i = 0;
	int sz = sizeof(arr) / sizeof(arr[0]);
	while (p < arr + sz)
	{
		printf("%d ", *p);
		p++;
	}
	return 0;
}

通过监视,我们可以看到,*p最开始所指向的,是arr数组的第一个元素,由于arr默认是数组第一个元素的地址,所以p<arr+sz就是说从数组的第一个元素地址开始,直到数组的最后一个元素地址。

6.野指针

 野指针,是指针指向的位置不可知,随机,不正确,没有明确限制

6.1指针成因

6.1.1指针未初始化

int *p;
//这里没有给指针进行赋值,也就是未初始化,默认为随机值

6.1.2指针越界访问

int arr[10]={0};
int *p=&arr[0];
int i=0;
for(i=0;i<=11;i++)
{
    *(p++)=i;
}
这样写,当p增加了11,也就是数组后面的第一个地址,这时候,指针就是越界访问
虽然那个地址可能没有存东西,即使赋值也不影响,但是也有可能已经被其他变量等使用
这时候越界访问就是一种违规操作,编译器就会报错

6.1.3指针指向的空间释放

#include <stdio.h>
int* test()
{
	int n = 100;
	return &n;
}
int main()
{
	int* p = test();
	printf("%d\n", *p);
	return 0;
}
这里的野指针,是因为n变量分配的内存空间是被释放了的,虽然那块区域暂时还没被覆盖
可能后面输出时,还能用,但是一旦程序复杂化,其他变量占用这块空间
就会让这个地址的内容产生变化。

6.2规避野指针

6.2.1指针初始化

第一个,就是直接赋予指针对应的地址

int *p=&n;

第二个,是当不清楚应该给什么地址时,赋值NULL。

6.2.2避免指针越界

不要超出申请的内存空间范围进行访问

6.2.3指针变量不使用时,及时置NULL,指针使用之前检查有效性

因为如果指针变量不再使用,但是又偏偏还指向了一片空间,这样当别的变量申请内存空间时,很可能会让这个指针变量指向的变量出现了变化,这样会显得混乱,所以赋予NULL

当然由于,NULL会让编译器报错,所以使用指针变量前要判断一下是否是NULL,不是再使用,是的话要先让其指向一片正规的内存空间。比如当p自增,越界了,可以用NULL,这样就不会报错了。

int *p=&a;

p=NULL;//赋予NULL地址,
while(p!=NULL);//判断是否是NULL

6.2.4避免返回局部变量的地址

注意,跟越界访问不同的是,越界访问是超过了程序申请的内存空间范围,而这里之所以空间释放了却能访问,虽然值不一定对,是因为指针变量存的是地址,虽然函数调用结束后,那片空间被释放了,但是那个地址的内容还没被覆盖,只是不再代表那个局部变量了。

7assert断言

assert.h 定义了assert(),用于在运行时确保程序符合指定条件,如果不符合,就报错终止运行。这个宏常常被称为“断言”。

assert(表达式),接受一个表达式作为参数。表达式为真,则程序继续执行,若表达式为假,则程序报错,并在错误信息中stderr中写入没有通过的表达式,以及包含这个表达式的文件名和行号

优点:

能自动标识文件和出问题的行号,还有一种无需更改代码就能开启或关闭assert()的机制,如果不需要断言,可以加上 #define NDBUG ,可以让编译器自动禁用文件中assert语句。

唯一缺点是会增加程序运行时间,一般debug中使用,release中禁用即可,vs这样的集成开发环境中,release版本中,直接就是优化掉了,不会影响程序效率。

 8.指针的使用和传址调用

8.1传址调用

实参传递给形参的时候,形参会单独创建一份临时空间来接受实参,对形参的修改不影响实参。所以如果在函数里进行变量值交换,不会影响到实参。

如果要函数调用中,将main函数的实参进行改变,可以将实参的地址传递给函数的形参,如此,直接用指针进行交换,由于指针变量存储的是变量的地址,改变指针也可以改变对应地址所存储的变量,于是就可以改变实参的值了。

这种函数调用方式,叫做传址调用。

8.2strlen的模拟实现

可以依靠指针-指针的算法。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值