C语言指针及指针进阶

本章为指针入门,指针进阶地址:http://t.csdn.cn/Bj6mr

目录

1、指针是什么

2、一个单元是多少个字节、如何编址(这里是截了比特的ppt

3、指针的类型有什么用

4、野指针

1、指针未初始化

2、指针的越界访问

 3、已经被销毁的地址还是再用

 4、如何规避野指针

5、指针运算

1、指针+-整数

2、指针-指针 

 3、指针的关系运算

 6、数组名是什么

1、&arr 

 2、sizeof(arr) 

 7、二级指针

8、指针数组

1、指针是什么

我的理解就是存放了一个变量的地址

指针是个变量,存放内存单元的地址(编号),如图所示。

编写一个代码测试一下

首先先创建一个整形变量,在里面存放一个10,之后创建一个整形指针存放a的地址。

之后分别读出a的地址,与p存储的数据,可以看见读出的两个地址是一样的。

之后取指针p的地址,可以看出p也是有地址的。

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int a=10;
	int *p=&a;
	printf("a=%d\r\n",*p);
	printf("a地址=%p\r\n",&a);
	printf("p存储的地址=%p\r\n",p);
	if(&a==p)
	{
		printf("是同一个地址\r\n");
	}
	printf("p的地址=%p\r\n",&p);
	getchar();
	return 0;
}

运行结果

总结:指针也是一个变量,但是是用来存放地址的变量。(存放在指针中的值都被当成地址处理)。


2、一个单元是多少个字节、如何编址(这里是截了比特的ppt

总结一下就是32位的机器有32根地址线,就有32位地址,这32位地址需要用4个字节来存储,所以指针的大小就是4个字节=2的32次方,有4GB的K线进行编写。

64位机器指针就是8字节

3、指针的类型有什么用

我们知道变量的类型有整形、浮点型、字符型等,那么指针有没有类型,如果有类型的话指针的类型有什么用呢,他存放的不是地址吗。那他的类型有什么意义。

写个代码测一下

首先我们创建一个int变量、一个char指针、一个int指针

用两个指针分别保存int变量的地址并且打印出来地址与存储值

#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
int main()
{
	int n = 65538;
	char *pc = (char*)&n;
	int *pi = &n;
	printf("n的地址=	  %p\n", &n);
	printf("拿char类型存储a地址为:  %p\n",pc);
	printf("拿char类型存储a地址+1为:%p\n", pc+1);
	printf("拿int类型存储a地址为:	 %p\n", pi);
	printf("拿int类型存储a地址+1为: %p\n", pi+1);
	printf("拿char类型指针解应用n: %d\n", *pc);
	printf("拿int类型指针解应用n: %d\n", *pi);
	printf("pi的地址=%p\n", &pc);
	printf("pc的地址=%p\n", &pi);
	getchar();
	return 0;
}

 这里我先打印了n的地址,之后分别拿int指针和char指针存储了n的地址,可以看见打印出来的地址是一样的,这也就说明了指针类型并不会影响地址的存储。

但是对pc+1和pi+1后得到的地址不一样,这就说明了指针类型会影响指针的步距。

 之后对pc和pi进行一个解引用,可以看出int类的可以打印出65538但是char类型的只能打印出2,这就证明了指针类型还会影响,指针能操作的权限。char类型就只能控制4个字节。65536在弟五位。

直接看地址

int指针

int main()
{
	int a=0x11223344;
	int* pi=&a;
	char* pc=&a;
	*pi=0;
	getchar();
	return 0;
}

取a的地址,里面数据为11 22 33 44

用int指针将其改为0这里四个字节全变为了0。

 char指针:只有低字节变为了00,说明char指针只影响了一个字节。

4、野指针

概念: 野指针就是指针指向的位置是不可知的(随机的、不正确的、没有明确限制的)

野指针的成因:1.指针未初始化   2.指针越界访问  3.指针指向的空间已经被释放了

1、指针未初始化

//局部变量指针未初始化,默认为随机值

声明指针变量后没有初始化为NULL,导致了地址为随机值。

int main()
{
	int  *pi;//局部变量指针未初始化,默认为随机值
	char *pc=NULL;
	getchar();
	return 0;
}

2、指针的越界访问

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

10位的数组,每次p++跳过一个字节,这里循环了12次,会直接超出数组的范围,占用其他内存空间。

 3、已经被销毁的地址还是再用

int* test()
{
	int a=0;
	return &a;
}
int main()
{
	int* p=test();
	*p=30;
	return 0;
}

虽然a的地址被返回了,但是这个局部变量出了函数之后就被销毁了,地址也被系统回收了,但是这个地址还是在我们手中,并且我们给他写了值,这个时候虽然你能用但是是违法的,就已经算是野指针了。

 4、如何规避野指针

通过上面三点总结一下怎么规避

1. 指针初始化(1、指针没有初始化指向了未知地址
2. 小心指针越界(2、数组多写越界了
3. 指针指向空间释放即使置NULL
4. 指针使用之前检查有效性(3、被回收了还在用

#include <stdio.h>
int main()
{
int *p = NULL;
//....
int a = 10;
p = &a;
if(p != NULL)
{
*p = 20;
}
return 0;
}

5、指针运算

1、指针+-整数 2、指针-指针  3、指针的关系运算

1、指针+-整数

通过指针遍历整个数组

P++ int类型指针 ++之后直接让整个指针跳了4个字节

int main()
{
	int arr[10]={0,1,2,3,4,5,6,7,8,9};
	int i=0;
	int sz=sizeof(arr)/sizeof(arr[0]);
	int* p=arr; //arr直接出现为首元素地址
	for(i=0;i<sz;i++)
	{
		printf("%d ",*p);
		p++;
	}
	getchar();
	return 0;
}

2、指针-指针 

两个整形指针一个存储arr首元素地址,一个存储末尾元素地址,两个相减。

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

结果为9,两个地址相减直接为元素个数了

 用指针-指针的方式模拟实现一个strlen函数

在函数内创建两个指针,都指向头,之后一个指针解引用判断是否当前位置符号为\0,如果一直不为\0就一直循环++位置,为\0之后就退出循环,之后用尾指针减去头指针,就能直接得到数组长度。

int My_Strlen(char* str)
{
	char* start=str;
	char* end=str;
	while((*end) != '\0')
	{
		end++;
	}
	return end-start;
}

int main()
{
	int len=0;
	char arr[]="My Strlen";
	len=My_Strlen(arr);
	printf("arr len=%d",len);
	getchar();
	return 0;
}

 3、指针的关系运算

指针比较大小:通过比较地址大小吧一整个数组值写为0;

/*1、创建一个整形指针,让他指向数组首地址,让其与尾地址比较大小*/

/*2、如果i指向的地址小于arr[5]的地址,则解引用将其内容写为0*/

                                    

int main()
{
	int* i=NULL;
	int a=0;
	int arr[]={1,1,2,3,4,5,6};
	int sz=sizeof(arr)/sizeof(arr[0]);
	for(i=arr;i<&arr[sz];i++)
	{
		*i=0;

	}
	for(a=0;a<sz;a++)
	{
			printf("%d ",arr[a]);
	}
	getchar();
	return 0;
}

输出结果:

 c语言标准规定了

标准规定:允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较

 如图所示:p1允许与p2比较不能与p3比较,可以向后越界一个,不可以向前越界。这样写大部分编译器可以运行,但是不符合c语言语法。

 6、数组名是什么

在大部分的情况下数组名单独出现就是首元素的地址但是有两种情况下不是。

1、&arr:代表的是整个数组的地址

2、sizeof(arr):这个代表数组大小,求数组长度的时候会用到

1、&arr 

可以看出来直接取地址虽然和首元素地址一样,但是+1的时候一个跳过的是一个元素一个跳过的是整个数组。

int main()
{
	int arr[5]={0,1,2,3,4};
	printf("%p\n",arr);
	printf("%p\n",&arr[0]);
	printf("%p\n",&arr);
	printf("%p\n",arr+1);
	printf("%p\n",&arr+1);
	getchar();
	return 0;
}

 2、sizeof(arr) 

很明显计算的是整个数组的字节数

int main()
{
	int arr[5]={0,1,2,3,4};
	int	len=sizeof(arr);
	int sz=sizeof(arr)/sizeof(arr[0]);
	printf("%d %d\n",len,sz);
	getchar();
	return 0;
}

 7、二级指针

 2级指针就是存储了1级指针的地址。

/*1、将一级指针pi的地址存入二级指针ppi*/

/*2、将变量b的地址通过二级指针解引用赋值进去*/

将pi内存储的地址改为了int b的地址,所以解引用之后打印为20。

其中有一个**ppi的语句:等于就是2次解引用,先通过解引用找到了pi再对pi解引用这样就可以直接找到a我们可以直接写入一个整数。

int main()
{
	int a=10;
	int b=20;
	int* pi=&a;
	int** ppi=&pi;
	*ppi=&b;
    //**ppi=15;//直接找到a改为15
	printf("%d ",*pi);
    //printf("%d ",a); //测试一下是否和*pi找到的地方一样
	getchar();
	return 0;
}

 

8、指针数组

存放地址的一个数组,是一个数组

 这就是一个指针数组;int* arr3[5];

arr3是一个数组,有五个元素,每个元素是一个整形指针。

通过一个指针数组打印其数组值

/*1、写三个整形变量*/

/*2、创建一个指针数组将三个整形的地址放入*/

/*3、将元素(a,b,c的地址)解引用得到数据*/

int main()
{
	int a=10;
	int b=20;
	int c=30;
	int i=0;
	int* arr[3]={&a,&b,&c};
	for(i=0;i<3;i++)
	{
		printf("%d ",*(arr[i]));
	}
	getchar();
	return 0;
}

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值