C语言初阶(三)深入了解指针(1)

内存和地址

内存

我们知道计算机上CPU(中央处理器)在处理数据的时候, 数据是在内存中读取的,处理后的数

据也会放回内存中,内存被分为⼀个个的内存单元,每个内存单元的大小取1个字节。

计算机中常见的单位(补充): ⼀个比特位可以存储⼀个2进制的位1或者0

 每个内存单元都有⼀个编号(就相当于宿舍的门牌号),CPU通过编号就能找到这个内存空间

我们把内存单元的编号也称为地址

C语言中给地址起了新的名字叫:指针        内存单元的编号==地址==指针

编址

CPU访问内存中的某个字节空间,要知道这个字节空间在内存的什么位置,所以要给内存进行编址

计算机中的编址,并不是把每个字节的地址记录下来,而是通过硬件设计完成的

计算机内是有很多的硬件单元,硬件与硬件之间是互相独立的,而硬件单元是要互相协同工作的,至少相互之间要能够进行数据传递,那么如何通信呢?答案很简单,用"线"连起来

而CPU和内存之间也是有大量的数据交互的,所以,两者必须也用线连起来

今天关心一组线,叫做地址总线

简单理解,32位机器有32根地址总线, 每根线表示0,1【电脉冲有无】,就能表示2种含义

32根地址线,就能表示2^32种含义,每⼀种含义都代表⼀个地址

地址信息被下达给内存,就可以找到该地址对应的数据,将数据在通过数据总线传入CPU内寄存器

指针变量和地址

取地址操作符(&)

在C语言中创建变量其实就是向内存申请空间

那我们通过取地址操作符(&)拿到的地址是⼀个数值

比如:0x006FFD70,这个数值有时需要存储起来方便后期再使用,可以把它存放在指针变量中

指针变量也是⼀种变量,这种变量就是用来存放地址的,存放在指针变量中的值都会理解为地址

如何拆解指针类型

指针变量名左边写的是类型的对象。

例如int* , * 是在说明是指针变量,int 是在说明指向的是整型(int)类型的变量

解引用操作符(*)

我们只要拿到了地址(指针),就可以通过地址(指针)找到地址(指针) 指向的对象,这里必须学习⼀个操作符叫解引用操作符(*)。

*pa 的意思就是通过pa中存放的地址,找到指向的空间

指针变量的大小

32根地址线产生的2进制序列当做⼀个地址,⼀个地址就是32个bit位,需要4个字节才能存储

那么存放地址的指针变的大小就是4个字节的空间。

同理64位机器,⼀个地址就是64个⼆进制位组成的指针变量的大小就是8个字节

所以

32位平台下地址是32个bit位,指针变量大小是4个字节

64位平台下地址是64个bit位,指针变量大小是8个字节

注意指针变量的大小和类型是无关的,只要指针类型的变量,在相同的平台下,大小都是相同的

指针变量类型的意义

指针的类型决定了对指针解引用的时候有多大的权限(一次能操作几个字节)

比如: char* 的指针解引用就只能访问一个字节,而 int* 的指针的解引用就能访问四个字节

指针的类型决定了指针向前或者向后走一步有多大(距离)

指针+1,其实是跳过1个指针指向的元素,指针可以+1,那也可以-1

比如:char* 类型的指针变量+1跳过1个字节, int* 类型的指针变量+1跳过了4个字节

void* 指针

在指针类型中有⼀种类型是 void * 类型,可以理解为无具体类型的指针(泛型指针)

这种指针可以用来接受任意类型地址

但是void* 类型的指针不能直接进行指针的+-整数和解引用的运算

如果将int类型的变量的地址赋值给⼀个char*类型的指针变量,编译器会因类型不兼容给出警告

而使用void*类型就不会有这样的问题。

那么void* 类型的指针到底有什么用呢?

⼀般void* 类型的指针是使用在函数参数的部分,用来接收不同类型数据的地址,实现泛型编程的

效果,使得⼀个函数来处理多种类型的数据

const 修饰指针

const修饰变量

变量是可以修改的,如果把变量的地址交给一个指针变量,通过指针变量的也可以修改这个变量

但是如果我们希望一个变量不能被修改,就可以使用const进行修饰

被const修饰后,在语法上加了限制,在代码中对n进行修改,就会不符合语法规则,出现报错,致

使没法直接修改n

但是如果我们绕过这个变量,用它的指针去修改就能做到了

const修饰指针变量

const放在*的左边

const如果放在*的左边,修饰的是指针指向的内容,保证指针指向的内容不能通过指针来改变

但是指针变量本身的内容可变

const放在*的右边

const如果放在*的右边,修饰的是指针变量本身,保证了指针变量的内容不能修改

但是指针指向的内容,可以通过指针改变

指针运算

指针+-整数

因为数组在内存中是连续存放的,只要知道第⼀个元素的地址,顺藤摸⽠就能找到后⾯的所有元素

指针-指针

指针的关系运算

野指针

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

野指针成因

1.指针未初始化

2.指针越界访问

3.指针指向的空间释放

如何规避野指针

1.指针初始化

2.小心指针越界

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

4.避免返回局部变量的地址

assert断言

assert.h 头文件定义了宏assert() 

用于在运行时确保程序符合指定条件,不符合就报错终止运行,这个宏常常被称为“断言”

assert(p != NULL);

上面代码在程序运行到这⼀行语句时,验证变量 p 是否等于NULL 

如果确实不等于继续运行,否则就会终止运行,并且给出报错信息提示

NULL,程序 assert()宏接受⼀个表达式作为参数

如果该表达式为真(返回值非零), 不会产生任何作用,程序继续运行

如果该表达式为假(返回值为零),就会报错,在标准错误流stderr中写入⼀条错误信息,显示没

有通过的表达式,以及包含这个表达式的文件名和行号

assert()的好处:

能自动标识文件和出问题的行号,有⼀种无需更改代码就能开启或关闭 assert()的机制

如果确认程序没有问题,不需要再做断言,就在 #include 语句的前面,定义⼀个宏NDEBUG

然后重新编译程序,编译器就会禁用文件中所有的assert()语句

如果程序又出现问题,移除这条 #define NDEBUG指令(或者把它注释掉),再次编译,就能重

新启用assert()语句

assert()的缺点:

引入了额外的检查,增加了程序的运行时间

可以在Debug中使用,在Release版本中选择禁用,

VS这样的集成开发环境中, 在Release版本中的assert()语句,直接就是优化掉了

这样在debug版本写有利于程序员排查问题,Release版本不影响用户使用程序时的效率

指针的使用和传址调用

strlen的模拟实现

库函数strlen的功能是求字符串长度,统计的是字符串中\0 之前的字符的个数函数原型如下

 size_t strlen ( const char * str );

参数str接收⼀个字符串的起始地址,然后开始统计字符串中\0之前的字符个数,最终返回长度

如果要模拟实现只要从起始地址开始向后逐个字符的遍历,只要不是\0 字符,计数器就+1,直到

\0就停止

参考代码如下:

int my_strlen(const char * str)
 {
     int count = 0;
     assert(str);
     while(*str)
     {
         count++;
         str++;
     }
     return count;
 }
     int main()
 {
     int len = my_strlen("abcdef");
     printf("%d\n", len);
     return 0;
 }

传值调用和传址调用

学习指针的目的是使用指针解决问题,那什么问题,非指针不可呢?

例如:写⼀个函数,交换两个整型变量的值一番思考后,我们可能写出这样的代码:

#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\n", a, b);
 Swap1(a, b);
 printf("
交换后:
a=%d b=%d\n", a, b);
 return 0;
 }

在main函数内部,创建了a和b,在调用 Swap1函数时,将a和b传递给了Swap1函数,在Swap1函

数内部创建了形参x和y接收a和b的值,x和y确实接收到了a和b的值,不过x的地址和a的地址不一

样,y的地址和b的地址不⼀样,相当于x和y是独立的空间,那么在Swap1函数内部交换x和y的值,

不会影响a和b,当Swap1函数调用结束后回到main函数,a和b的没法交换

Swap1函数在使用的时候,是把变量本身直接传递给了函数,这种调用函数的方式我们之前在函数

的时候就知道了,这 种叫传值调用

结论:实参传递给形参的时候,形参会单独创建⼀份临时空间来接收实参,对形参的修改不影响实

 参,所以Swap1失败了。

我们现在要解决的就是当调用Swap函数的时候,Swap函数内部操作的就是main函数中的a和b,

直接将a和b的值交换了,那么就可以使用指针了,在main函数中将a和b的地址传递给Swap函数,

Swap函数里边通过地址间接的操作main函数中的a和b,并达到交换的效果就好了

#include <stdio.h>
void Swap2(int*px, int*py)
 {
     int tmp = 0;
     tmp = *px;
     *px = *py;
     *py = tmp;
 }
     int main()
 {
     int a = 0;
     int b = 0;
     scanf("%d %d", &a, &b);
     Swap2(&a, &b);
     printf("
    交换前:
     a=%d b=%d\n", a, b);
     printf("
    交换后:
     a=%d b=%d\n", a, b);
     return 0;
 }

可以看到Swap2顺利完成了任务,Swap2函数是将变量的地址传递给了函数

这种函数调用方式叫:传址调用, 可以让函数和主调函数之间建立真正的联系,在函数内部可以

修改主调函数中的变量

所以未来函数中只是需要主调函数中的变量值来实现计算,就可以采用传值调用

如果函数内部要修改主调函数中的变量的值,就需要传址调用

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值