指针与相关知识

1.什么是地址

在介绍指针之前,我们首先需要知道什么是地址?——CPU访问内存某个空间时,必须知道这段空间的位置,内存会给相同大小不同位置的空间编址,地址可以指向某段空间的位置。

2.什么是指针

2.1知道什么是地址后,回到C语言中。C语言在创建变量时,会向内存申请空间。

指针 本质是一种变量,与普通变量不同的是,普通变量存放的是数据,指针变量存放的是该数据存放的地址(这里放一张普通变量的图,一张指针变量的图)

2.2指针变量的格式

int a = x ;

int* pa = &a ;

int* 是指针变量的类型。* 说明变量p是指针,int 说明该指针指向的变量a的类型是int

同理,可以知道char a = x;  的指针为

char* pa = &a;

指针变量的类型只能与其取地址的普通变量的类型相同(void* 类型除外)

2.3解引用操作符*

*的作用是通过地址得到数据

例如:int a=4; 

            int* pa=&a;

            *pa = 0;

通过解引用操作符,可以将指针变量存放的地址转换为数据。上述代码中*pa其实就是a变量,*pa = 0;这个操作就是a = 0;。

2.4指针变量的大小

X86环境下指针变量的大小是4个字节,X64环境下指针变量的大小是8个字节。

地址的大小也是同理,X86环境下,一个地址占用4个字节;X64环境下,一个地址占用八个字节。

指针变量的大小与类型无关,只与运行环境有关,运行环境相同的话,不管是int* 的指针,还是char*的指针,所有指针变量的大小都是相同的。

3.指针变量类型的意义

3.1指针的解引用

上文提到过,指针的解引用就是将指针里存放的地址转换为该地址的数据。

int a = 0;

*pa = a;

我们都知道,int类型的变量占内存四个字节的空间。那么,*pa = 0时,改变的是多大的内存?

这由指针变量的类型决定。

int* pa = 0时,改变的是四个字节大小的内存,会将四个字节的地址全部改为0。

chat* pa = 0时,改变的是一个字节大小的内存,会将一个字节的地址全部改为0。

结论:所以指针变量的类型决定了该指针变量解引用时有多大权限(一次能改变多少字节)

3.2指针+-整数

(PPT里面的代码)

char* 类型的指针变量+1时,跳过一个字节;

int* 类型的指针变量+1时,跳过四个字节;

结论:指针的类型决定了指针向前或向后走一步的距离有多大

3.3void* 指针

void* 类型的指针可以接受任意类型的变量,但是不能进行指针的解引用和+-整数。

void* 类型的指针一般用来当做函数的参数,方便接收各种类型的数据

4.const修饰指针

4.1const修饰变量

变量是可以被修改的

int a = 0;

a = 1;

这样变量a就从0修改成1了。如果不想a被修改,就可以在int前面加上const。

const int a = 0;

这样在修改变量a时,编译器会报错,不让修改。但是,如果是通过指针修改,还是可以的

const int a = 0;

int* pa = &a;

*pa = 1;

2.2const 修饰指针变量

为了不让a被修改,我们可以用const修饰变量a的指针。const修饰指针变量时,有两种写法,一种是放在*的左边

const int * pa = &a;

一种是放在右边

int * const pa = &a;

这两种的区别是:

const放在左边,可以修改pa取地址的变量,不能修改变量的大小。

pa = &n;(✓)

*pa = 1;(×)


   可以把取地址a换成取地址n,但是不能修改a的内容


放在右边,可以修改变量的大小,不能修改pa取地址的变量。

*pa = 1;(✓)

pa = &n;(×)

    可以把a=0改成a=1。不能把取地址a改成取地址n

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));//p+i 这里就是指针+整数

}

return 0;

}

(运行结果图)

5.2指针-指针

指针-指针可以理解为日期-日期,就像4月2日-4月1日 = 1一样,指针 - 指针 = 两个指针之间的空间大小

#include <stdio. h>

3 int my _ strlen(char *s)

4 {

5 char *p = s;

6 while(*p != '\0' )

7 p++;

8 return p-s;

9 }

10

11 int main()

12 {

13 printf("%d\n", my _ strlen("abc"));

14 return 0;

15 }

指针存放地址时,在存放该地址的空间的最后,会加上一个\n作为结束标志。

所以在指针p增大到\n时,指针p作为连续指针已经是最大的了。再增大则会出现越界的现象,程序会报错。

5.3指针的关系运算

6.野指针

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

6.1野指针的成因

6.1.1指针未初始化

6.1.2指针越界访问

6.1.3指针指向的空间释放

6.2如何规避野指针

6.2.1指针初始化

如果知道指针需要包含哪个变量,则直接给指针取地址该变量;不知道哪个变量的话,可以给指针赋值NULL。NULL是C语言中定义的标识符常量,值是0。

6.2.2小心越界访问

一个程序向内存申请了哪些空间,指针也只能访问这些空间,不能超出范围访问,超出就是越界访问了。

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

当指针变量使用时,给它指向某一段空间;当指针不再使用时,给它置NULL,NULL指针一般不运行。

但是NULL只能防止指针自行运行,如果主动运用指针,则还是会发生野指针的运行。

并且NULL指针需要编译器进行判断

if(*p != NULL)

才可以运行;如果不判断,程序则会报错

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

编译器在运行函数时,会单独开辟一块空间(详细请看空间栈帧),在函数运行完成后,这块空间就会还给内存

因此,局部变量在函数完成后,会跟着函数一起被销毁地址,因此指针在指向局部变量时会因为该变量无地址而报错

7.assert宣言

#include<assert.h>

assert(p)        或    assert(表达式)

验证p或表达式是否为NULL。如果不是NULL,则程序正常运行,assert不发挥作用;如果是NULL,则assert会报错,阻止程序运行

assert一般用于程序进行debug。当确定程序没有bug后,可以在#include<assert.h>前面定义一个宏#define NDEBUG,这样编译器就会禁止程序中的assert

#define NDEBUG

#include<assert.h>

assert一般用于程序进行debug。修改完所以bug后,因为assert引用了额外的检查,增加了程序的运行时间,所以需要通过

#define NDEBUG

来禁止assert,不影响用户的使用效率

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

8.1 传值调用和传值调用

学习指针是为了解决问题,有什么问题一定要用指针呢?

例如:写一个交换两个整型的函数

#include<stdio.h>

void Swap1(int x,int y)

{

int tmp = x;

x = y;

y = tmp;

}

int main()

{

int a = 0;

int b = 0;

scanf("%d %d",&a,&b);

printf("交换前:a = %d,b = %d",a,b);

Swap1(a,b)

printf("交换后:a = %d,b = %d",a,b);

}

(结果图)

这种我们称为传值调用,因为函数在运行时会创建一个临时空间,(x,y)的内存地址与(a,b)的内存地址不同,函数传递给main函数的值是(x,y),实际上并没有改变(a,b)的值,所以交换前后的(a,b)没有互换

结论:实参 (a,b) 传递给形参 (x,y) 的时候,形参 (x,y) 会单独创建一份临时空间来接收实参 (a,b) ,对形参 (x,y) 的修改不影响实参 (a,b)

#include<stdio.h>

void Swap2(int* px,int* py)

{

int tmp = 0;

int tmp = *px;

*px = *py;

*py = tmp;

}

int main()

{

int a = 0;

int b = 0;

scanf("%d %d",&a,&b);

printf("交换前:a = %d,b = %d",a,b);

Swap1(&a,&b)

printf("交换后:a = %d,b = %d",a,b);

}

(结果图)

这种我们称为传址调用,函数把变量的地址传递给main函数

传址调用,可以让函数与主函数之间建立真正的联系,在函数内部可以修改主函数的变量


所以,当函数只需要对参数进行计算的时候,可以使用传值调用;当函数需要修改主函数的参数的时候,就需要使用传址调用。

9.二级指针

指针变量也是变量,既然是变量,也就有地址,二级指针用来存放指针变量的地址。

int a = 0;

int* pa = &a;

int* *ppa = &pa;

10.回调函数

完。

评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值