天天看点

C/C++ 学习笔记四(指针、数组)

什么是指针?

指针其实是内存单元地址

什么是指针变量?

指针变量是用于存储内存单元地址的变量

指针变量存储的实质

顾名思义,指针变量存储的是内存单元的值,即存储的值其实指向的一个特定类型内存区域的起始地址。

如下例子,整型变量a的值为123,其内存单元地址为0x104,指针变量存储的值是变量a的内存单元0x104。

指针变量指向了内存地址起始为0x104,长度为4个字节的内存单元。p也可以对这个4个单元进行操作。

C/C++ 学习笔记四(指针、数组)

c语言中,我们可以像普通整型数据一样,对指针变量进行运算。

指针变量的转换

普通类型的指针变量都可以直接赋值void *空类型指针,

但空类型指针需要强转才可以转成普通类型指针。

void 指针是一种特殊的指针,表示“无类型的指针”,因为其没有指定类型,所有它可以指向任何类型的数据,也就是说任何类型的指针可以直接复制给void指针。

但是void指针赋值给其他类型的指针时,必须强制转换

这是因为指定了类型的指针变量指向了内存的一块区域,但空类型指针无法确定指向内存区域的大小。

需要注意的是,不同类型指针变量相互转换时,需避免访问非法的内存区域,导致程序异常退出。

如下例子,chP指向了一段长度为1字节的变量a的内存区域,当其强制转换成int指针时,则超出了编译器分配的内存区域,程序会异常退出。

指针变量的算术运算

指针变量可以使用算术运算符实现自增减,如下例

假设&a地址为0x100,则上面例子的输出为

即使指针变量的算术运算为增减sizeof(数据类型)的大小。上例子中p的值为0x100,强转为char 后自增1,结果为0x101,强转为double 后自增1为,0x108(0x100+0x8)。

C/C++ 学习笔记四(指针、数组)

同理因为空指针类型无法得知其指向区域的长度,void *指针便无法进行增减操作。

C语言中,数组与指针是一种非常暧昧的关系,因数组和指针经常可以相互的转换,所以经常会将其两者混淆。

真正的事实是,两者拥有不同的存储结构,但引指针的灵活性,两者可以相互的引用、转换。

数组的存储结构

与指针的存储结构相比,数组在内存中占据的是连续的字节单元。即指针存储的长度根据计算机不同,是一个固定的大小 (32位4个字节、64位8个字节),数组存储的是一块连续的内存区域。

C/C++ 学习笔记四(指针、数组)

那为什么指针可以访问数组中元素?

这是因为数组标识符表示的是该数组的第一个元素的地址,当指针指向数组标识符时,便可以通过指针访问数组中的各个元素。

函数调用时数组作为参数时为地址传递

C语言的标准中规定:所有的数组在作为参数传递时,都转换成指向数组起始地址的指针,其他参数均采用值传递。

采用地址传递好处是形参不存在存储空间,编译系统不为形参分配内存,数组名或者指针便是一组连续空间的首地址。

例如下面例子输出的size都为4。

数组和指针其实并不是一个相同概念,虽然在日常的使用中,经常可以使用指针代替数组,用于遍历数组的元素,例如

再来一个例子,可以证明数组并不是指针:

执行上面代码会提示“a”: 重定义;不同的间接寻址类型

在使用extern int a时,编译器认为a是一个在外部声明的整型指针变量,但f1.c中,a是一个长度为3的整型数组,在32位系统系统下,int 长度为4字节,而int [3]长度为4*3 = 12字节。

再看一个例子

程序执行后打印different。这是因为a指向的是存储于数据段静态变量hello,arr则指向编译器分配的内存空间。两者的值并不一样。

对于数组而言,编译器已经为数组分配了一定的空间以及对应的地址,通过数组地址的偏移,可以访问该数组的元素。

而指针,编译器为其分配了空间,用于存储地址值。而对于存放在其中的值,只有在程序运行时才能知道。

1. 使用指针前必须初始化,否则会指向错误的内存区域,导致程序异常。

2.使用NULL指针作为函数调用失败的返回值。类似malloc函数,当分配内存失败时返回NULL,用以表示为一种异常情况。

3.在不知内存区域具体类型情况下,避免对void 指针进行算术操作

例如,下例子对p任何的操作都会导致程序出错

4. 不同类型指针转换时,注意不超出编译器分配的内存区域。

如下例子,ip使用了超出了编译器分配的内存,会导致程序异常退出

5.避免指针和整型数据的直接转换。

1.指针变量是变量,存储内存地址的变量。

3.数组存储的是一段连续的内存区域

4.数组标识符存储了,一段内存区域的起始地址

5.数组作为参数传递时是地址传递,其他类型则为值传递