C语言 初识指针

1.指针的定义

计算机的CPU在处理数据时,其所需的数据是从内存中读取的,而为了准确无误的读取其所需要的数据,内存被划分为一个个内存单元,每个内存单元都是一个字节,而每一个内存单元都有自己的一个编号,这就方便了CPU快速地找到需要的数据。这一个内存单元的编号就是C语言中的指针

2.指针变量和地址

在C语言中,每当创建一个变量的时候,就是向内存申请了一块空间,这块空间用来存放你所创建的变量。

int main()
{
	int a = 0;
	return 0;
}

这一块就是内存划分给变量a的地址。

取地址操作符&

如何得到这一块地址的编号呢?这里就用到了取地址操作符(&)了。

#include<stdio.h>
int main()
{
	int a = 1;
	printf("%p", &a);
	return 0;
}

一个int类型的整数占4个字节,&a取出来的是较小的第一个字节,有了第一个字节的地址,a的位置推一下就能知道了。

指针变量

上面取出了a的地址,如果以后要用到a的地址,那么这时就需要一个变量来存储a的地址,这个变量就叫做指针变量

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

指针变量也是一种变量,存放在指针变量里面的值都会被理解为地址。

那么这种变量的类型就是变量名称的前面的东西,例如上面p的类型就是int*

3.解引用操作符

2中指针变量的初始化时用到了一个*号,这就是一个解引用的操作符。

我们拿到了一个变量的地址时,如果想要根据这个地址找到它所指向的变量并使用这个变量,那么我们就要用到解引用操作符。

int main()
{
	int a = 100;
	int* pa = &a;
	*pa = 0;
	return 0;
}

*pa的意思就是通过pa这个地址,找到其所指向的变量就是a,*pa就是a,然后*pa=0,其实就是把a的变量赋值为0,可以到编译器运行一下结果,看一下是不是a的最终值变成了0。

4.指针的大小

在32位机器中,共有32根数据总线,一个数据总线传输一个bit位大小的信息,那么32根就是32bit,就是说在32位机器中,一个指针的大小就是4个字节。

按照上面的说法,在64位机器中,一个指针的大小就是8个字节。

#include <stdio.h>

int main()
 {
 printf("%zd\n", sizeof(char *));
 printf("%zd\n", sizeof(short *));
 printf("%zd\n", sizeof(int *));
 printf("%zd\n", sizeof(double *));
 return 0;
 }

可以将编译器改成x86  x64在32位 64位环境下去观察一下上述类型的大小是不是理论所说。

注意:指针变量的大小只跟位数环境有关,跟其是什么类型的指针变量无关。

5.指针变量类型及其意义

指针的变量的分法就是数据类型后面加上一个解引用操作符,那分指针变量类型肯定不是闲来无事,肯定具有它存在的意义。

#include <stdio.h>
 int main()
 {
 int n = 0x11223344;
 int *pi = &n; 
*pi = 0;   
return 0;
 }
#include <stdio.h>
 int main()
 {
 int n = 0x11223344;
 int *pa = (char *)&n; 
*pa = 0;   
return 0;
 }

上述两段代码唯一的不同就是第二段代码中pa本该是一个int*型的指针,那后面加了一个(char*),给它强转成了一个char*类型的指针,下面在对其进行解引用操作,第一段代码类型是int*,那么它可以一下子修改四个字节的空间,就是把地址改为了0x00000000,第二段代码是char*类型的指针,那么它只可以修改一个字节的地址,这就是两个不同类型的指针所有的不同的性质。大家可以去试一下最终结果。

指针和整数运算

有了上面对指针类型的了解,指针与整数的运算也就比较好理解了。

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

可以看到char*类型的指针加1后会跳过一个字节

而int*类型的指针加1则会跳过四个字节。

特殊类型的指针

在指针类型当中存在着一种特殊类型的指针void*,它是可以接受任何类型变量的地址,但是它却没有自己的大小,它不能进行解引用操作,也不能与整数进行运算。

不能进行这一系列运算,那void*是用来干嘛的呢?

void*指针一般会出现在函数参数的部分,用来接受不同类型的地址,可以使一个函数来处理不同的类型的数据。

const修饰指针变量

变量是可以修改的,那么如何让变量不可被修改呢?就是加上一个const,用const修饰变量。

 #include <stdio.h>
 int main()
 {
 int m = 0;
 m = 20;//m是可以修改的
 
const int n = 0;
 n = 20;//n是不能被修改的
 
return 0;
 }

但是这样可以绕过变量,把变量交给一个指针,通过指针同样可以修改变量的值。

 #include <stdio.h>
 int main()
 {
 const int n = 0;
 printf("n = %d\n", n);
 int*p = &n;
 *p = 20;
 printf("n = %d\n", n);
 return 0;
 }

如果想让这个变量不可被修改,那么就要学会用const修饰指针变量。

const在修饰指针变量时,const放在*左边和右边是不一样的。

void test2()
 {
 int n = 10;
 int m = 20;
 const int* p = &n;
 *p = 20;//ok?
 p = &m; //ok?
 }

const 放在*左边时,解引用指针来改变变量值的操作不被允许。

void test3()
{
	int n = 10;
	int m = 20;
	int* const p = &n;
	*p = 20; //ok?
	p = &m;  //ok?
}

const在*右边时,通过修改地址来改变变量值的操作不被允许。

void test4()
 {
 int n = 10;
 int m = 20;
 int const * const p = &n;
 *p = 20; //ok?
 p = &m;  //ok?
 }

而在两边都有const时,两种方法均不被允许。

所以可以得出一个结论:const修饰谁,谁不能被修改。

6.指针的加减运算

1.指针+-整数

根据上述指针变量类型及其意义,可以用int类型的指针加减整数打印int类型的数组。每加一就会跳过四个字节指向数组中下标+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));//p+i 指针加减整数
	}
	return 0;
}

2.指针加减指针

指针和整数的运算最后的值还是指针,指针与指针运算得到的值是两指针之间元素的个数。

模拟实现strlen

#include <stdio.h>
 int my_strlen(char *s)
 {
 char *p = s;
 while(*p != '\0' )
 p++;
 return p-s;
 }
 int main()
 {
 printf("%d\n", my_strlen("abc"));
 return 0;
 }

函数内部创建一个指针变量让其等于传来的指针变量,接着让创建的指针p++,直到遇见‘\0’结束,然后返回p-s即是元素的个数。

7.野指针

所谓野指针指的就是不合法的指针(指向的位置不可知),这些指针可能会对内存非法访问。

野指针的成因

1.未初始化

#include <stdio.h>
 int main()
 {        
int *p;//
局部变量指针未初始化,默认为随机值
 
*p = 20;
 return 0;
 }

2.越界访问

 #include <stdio.h>
 int main()
 {
 int arr[10] = {0};
 int *p = &arr[0];
 int i = 0;
 for(i=0; i<=11; i++)
 {
 *(p++) = i;
 }
 return 0;
 }

arr大小只有10,i==11的时候就越界访问了,此时p就是野指针。

3.指向的空间被释放

 #include <stdio.h>
 int* test()
 {
 int n = 100;
 return &n;
 }
 int main()
 {
 int*p = test();
 printf("%d\n", *p);
 return 0;
 }

这里令*p等于一个函数的返回值,函数在运行完毕以后便归还了其创建的变量,那块空间被释放掉了,所以*p接收到的是一块没有东西的空间,那p就成了野指针。

注:我们应该养成良好的习惯,规避野指针的形成。要给暂时不用的野指针初始化置为NULL,不要越界访问,在用完指针不再使用时,及时置为NULL。

再介绍一种方法来规避野指针。

assert断言

需要用到头文件<assert.h>

assert(p != NULL);

代码运行到次就会判断()里面的语句,如果为真,那么接着往下运行,如果为假,那么程序中止。

当写完代码没有问题了,如果想要取消assert的功能,可以直接用#define NDEBUG来取消断言。

  • 23
    点赞
  • 40
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值