8.C基础_指针基础

指针概述

指针存放的都是首地址。

1、定义与初始化

形式:<数据类型>* <变量名> = <地址>;

int a = 10; 
int *p = &a;

指针的类型不同,p++时的偏移地址量不同,偏移地址 = sizeof(类型)Byte

注意点:

  • 指针的类型要与数据的类型保持一致,a为int,那么p就是int*," * " 称为指针运算符
  • 指针应该赋值地址,&为取地址符,存放的是数据的首地址
  • 指针变量不能赋值普通整数(0除外),不能赋值p=0x1234,而应该赋值p=(类型*)0x1234

2、目标与解引用

目标:指针指向的内存区域的数据。上述中,a就是目标。

解引用(间接访问):使用*p对目标进行访问," * "称为解引用。上述中*p就是解引用

注意:在使用(*p)++时,需要加括号。" * "的优先级低于 " ++ " " -- "

对于上述代码,指针的几种方式有:p、*p、&p,关系如下:

  • p: 指针变量, 它的内容是地址量
  • *p: 指针的目标,它的内容是数据
  • &p: 指针变量占用的存储区域的地址,是个常量

3、指针大小与寻址空间

指针的大小与系统的位数有关。

  • 32位的系统有32根地址线,因此指针大小为32位,4字节
  • 64位的系统有64根地址线,因此指针大小为64位,8字节

注意:指针存放的是地址,地址的大小是固定的。不论是什么类型的指针,大小都为4或8字节。

寻址空间与指针的位数有关。

  • 32位的指针大小可以寻址0~2^32-1范围
  • 64位的指针大小可以寻址0~2^64-1范围

4、空指针

空指针并不是指没有赋值的指针,而是赋值为NULL的指针。NULL就是(void*)0

使用空指针的原因:

  • 指针在定义时没有初始值,值是不确定的。给一个空指针可以防止野指针的出现。
  • 空指针指向0,这个地址不允许访问,访问一定出现段错误,因此可以很快发现指针使用错误

良好的编程习惯:

  • 在定义指针时,要么是赋值非空指针,要么是赋值NULL,防止野指针出现
  • 在函数传入指针参数时,首先判断是否为空,防止后续产生段错误。

5、野指针

野指针指的是指向了一个不确定空间的指针。

有一些野指针在编译上不会产生任何问题,逻辑上却会产生很严重的莫名奇妙的bug

产生野指针的原因:

  • 指针没有初始化,比如只定义了int* p;而没有赋值NULL或其他值
  • 指针越界访问,比如char a[3]最大索引为2,然而去访问了a[3],产生了越界,就是野指针
  • 指针指向空间被释放,比如malloc开辟的空间,free之后再去访问,这就是野指针

指针运算

1、指针±常数

指针±常数的符号有:+、- 、++、--

指针±常数后的值与指针的类型有关,参与运算的是指针保存的地址。

  • 运算结果=当前位置 + 常数 * sizeof(类型),该公式适用全部指针,包括多级指针

对如下程序进行分析:

int  a[5] = {1,2,3,4,5};
int* p = a;
p++;

这里的p为a的首地址,假设为0x00。p++之后,p的值 = 0x00+1*sizeof(int),这个值就是&a[1]

注意:

  • 这里的p应该与a类型一致,int对应int,不能用char* p去访问int a[5]
  • " 指针+常数 "代表指针向高地址移动," 指针-常数 "代表指针向低地址移动

2、指针-指针

指针-指针运算需要两个参与运算的指针的类型相同,这才是有效运算。运算结果代表这两个指针之间相差了多少个元素。比如上述代码,p++之后值为0x04,p的值为0x00,相减之后并不为4,而是为1,说明p++与p之间相差了一个int元素。代码验证如下:

3、自加自减运算注意点

" ++ "、" -- "的运算优先级比" * "的优先级要高,所以要注意运算的结合顺序。

下面以一个小代码为基础分析*p++、(*p)++、++p、++*p的含义

int  a[5] = {1,2,3,4,5};
int* p = a;

3.1 *p++含义

p++先运算,但++为先用后加,所以运算的值是*p,结果为1。之后p指针+1,指向2

训练:分析*p++ = 3的值

p++先结合,先用再加,所以当前*p=1,之后将3进行赋值,所以a[0]变为3,最后指针+1,指向2

3.2 (*p)++含义

*p先运算,相当于把1取出来,之后再++。同样是先用再加,所以运算值为1,之后1+1变为2

3.3 *++p含义

++p先结合,先加后用。所以运算的值是*(p+1),结果为2,最终p指向2

3.4 ++*p含义

这时++与*已经不在有优先级的事情,因为没有运算的考虑点。在前面*p++时,我们不知道是先和*还是先和++结合,所以考虑优先级。对于++*p,p只能和*结合,所以含义如下:

*p结合之后再++,是先加再用,所以运算结果是1+1=2,最终a[0]=2,*p依旧指向a[0]

4、指针比较

指针可以进行比较,运算符有:>、<、==、!=

指针比较的含义:

  • 与0比较,判断指针是否为空指针NULL
  • 与正常指针比较,存放地址大的指针>存放地址小的指针,如p1=&a[0],p2=&a[1],则p1<p2

其他指针

1、多级指针

1.1 多级指针的含义

多级指针就是指向指针的指针,它存放的是指针变量的地址。因为存放的依旧是地址,所以多级指针的大小为4/8字节,与指针变量大小一样。

  • 指向数据的指针是一级指针,用int* p = &a表示
  • 指向指针的指针是二级指针,用int** pp = &p表示

 1.2 多级指针偏移量

多级指针的偏移量同样适用公式" 运算结果=当前位置 + 常数 * sizeof(类型) "。

  • 二级指针存放的是int*,所以偏移sizeof(int*)大小,int*是地址,所以大小为4/8字节
  • 三级指针存放的是int**,所以偏移sizeof(int**)大小,int**是地址,所以大小为4/8字节

1.3 练习:使用多级指针访问指针数组。

分析如下代码的运行结果:

#include <stdio.h>

int main(){
	
	char* a[] = {"work","at","alibaba"};
	char** pa = a;
	pa++;
	printf("%s",*pa);
	
	return 0;
}

分析:a为一个指针数组,pa是二级指针,指向指针数组头。pa++就是数组偏移,所以指向了a[1]这个位置,此时*pa就是将at的首地址取出,printf中的%s只需首地址即可打印字符串,因此输出结果是at。

2、void指针

2.1 void指针的含义

形式:void* <变量名> 如:void* p;

void指针又称为万能指针、泛指针、通用指针。当一个指针的类型为void时,所指向的对象仅仅是一个地址,而不代表任何类型,系统并不知道指针的偏移值大小,因此void指针不能进行算术运算,如"++"运算

2.2 void指针使用规则

  • 使用前必须进行初始化,赋值时,指针类型可为任意
  • 解引用必须进行强制转换,以告诉系统指针的偏移值大小。

2.3 解引用时发生了什么

当*p执行时,系统根据p的类型来判断拿出几个字节,来组成一个完整的数据。比如int*p就拿出4个字节,char*p就拿出1个字节。对于void类型,系统不知道要拿出多少字节,所以需要强制转换,让系统知道这件事。

3、const修饰的指针

3.1 const修饰的指针的种类

  • 指针本身不能修改,即:p的值不能改。这称为" 常量化指针变量 "
  • 指针指向的数据不能修改,即:*p的值不能改。这称为" 常量化指针目标 "
  • 指针本身和指向的数据都不能修改,即:p和*p的值都不能改

3.2 const 指针各个种类的命名规则

就近原则:const修饰谁,谁不能改变。

  • 常量化指针变量:int* const p,const修饰的是p,所以p不能改
  • 常量化指针目标:const int* p,const修饰的是*p,所以*p不能改
  • 两者结合:const int* const p,const修饰了*p和p,所以都不能改

4、main参数(argc,argv)

main的完整函数形式:int main(int argc,const char* argv[])

  • argc:命令行参数的个数,就是argv的数组长度。
  • argv:参数的指针数组,存放的是参数字符串的首地址。

注意:argv[0]是程序的全名,argv[1]是第一个参数的首地址,程序的全名也算一个参数个数。

编程规范:在main真正执行之前加上参数正确性判断。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值