byte
都进行编号,这样的话一个编号就对应一字节的存储单元,程序只需要知道数据的编号和数据类型就能够找到想要的数据并进行操作了;这里的编号就是地址。综上所述,我们在使用C语言编程过程中对变量的命名在经过编译之后就是直接对地址内容的操作了(通过寄存器中的地址直接找到存储单元),也就是说变量==地址内容;
通过变量名对变量的访问称为直接访问:
int i = 2;
printf("%d", i);//直接访问
指针==地址,地址就像是指向一个变量(也就是一个存储单元)的箭头,可以根据箭头找到变量。
C语言中允许对内存空间的直接操作,也就是说,只要用户知道一个地址就可以直接去访问该地址的内容(将用户的指针值写入寻址寄存器找到存储单元),这种访问方式称为间接访问:*(指针值)
int i = 2;
printf("%d ", *(&i));//间接访问
//& 取地址;* 解引用
//如果 &i == 0x0f000011
printf("%d ", *(0x0f000011));
//输出结果 为 2 2
指针值的三种出现方式:
&
操作符,得到一个变量的地址;其中,对编译器自动定位的地址,用户不需要关心,系统对变量的定位对用户是透明的,用户对变量名直接操作就是对一个地址内容直接操作;
对于前两种方式得到一个地址之后,如何来存储呢?
指针变量:存放地址的变量;
如上图,有以下访问操作
操作 | 内容 | 说明 |
---|---|---|
变量名 | 变量值 | 直接访问 |
&变量名 | 地址2 | 取址操作符的作用 |
—— | —— | —— |
指针名 | 指针值 | 直接访问,可以存储地址2 |
*(地址2) | 变量值 | 间接访问 |
*(指针名) | 变量值 | 间接访问 |
—— | —— | —— |
&指针名 | 地址1 | 可以用多重指针存储地址1 |
程序中可操作的均为变量名/指针名
,以及操作符& *
;
指针变量的定义:基类型 *指针名
指针变量是一类特殊的变量,其数据类型有两个部分:
*
确定变量为指针类型,存储内容为一个地址,对一个机器其地址的大小是确定的,32位机器的地址长度为32b
(也就是一个int的大小);基类型
确定指针指向的空间的数据类型,基类型保证了对指针的移动,增减的操作的正确性。int a;//普通变量
int *p;//指针变量p,指向一个整型空间,以四字节为单位处理p存储的地址指向的存储单元
a = 999;//直接引用,a对应存储单元直接设为999
p = &a;//直接引用,p对应存储单元直接设为a的地址
*p = 1;//间接引用,使得指针p指向的存储单元(即a)设为1
printf("%d", a); //输出 1
函数的定义中要设置形参列表,指针类型同样是一种数据类型可以作为函数的形参出现,函数声明中有基类型 * 形参指针名
;
在函数调用语句中,传递的实参应该是基类型的一个地址,(实参可以是指针变量或者直接取地址得到的常量)被调用函数会在调用栈中复制一份这个地址,这样的间接访问能够实现对调用函数中的变量值进行编辑;
可以发现在指针的传递过程中,地址被复制了多份并存储在不同的存储单元中,并且有不同的名字,但对这些指针变量进行解引用,操作的均为目标数据的数据单元
指针同样可以指向函数,你可以声明一个指针是用来指向函数的,从而选择要赋值的函数名,从而实现选择要调用的函数,本质上来讲还是指针灵活运用的一种,有点面向对象语言的模板的意思
在解决问题、处理数据的过程中,我们总会遇到这样的情况:
以下我们称这样的一组数据为批量数据。。。
如果我们对每一个单独的数据进行命名,再按照名称挨个处理,我们的源程序就会重复的为每一个数据进行操作,这是不现实的;
我们需要一种方式将这样的批量数据按照一定结构进行存储,使得程序在处理完第一个数据之后能够自动的找到下一个数据,从而使用循环结构对批量数据进行处理。
组织数据的方式多种多样,不同结构有其特点和独有的操作方式,这是计算机能够处理海量数据的基础理论之一,我们会在《数据结构》课程中学习
如何组织数据?最简单的就是顺序存储,我们将批量数据一个挨一个的存储在内存中连续的一块地址空间;
如何访问批量数据呢?只需要记录这块连续空间的起始地址,由于批量数据具有相同的数据类型,想要访问的第i个数据的地址
为起始地址+i*数据类型的长度
(注意,我们对批量数据的计数是从零开始的);
在程序设计中如何实现这样的存储结构呢?程序设计语言提供基础的顺序存储结构 ——>数组
定义:数据类型 数组名[常量表达式]
含义:在内存中开辟一块连续的空间,大小为常量表达式*数据类型长度
,其中数组名也就是这块空间的起始地址,也就是一个指针,但特别的,数组名为常量指针
,它指向的位置固定,不能被赋值!!!
在定义中必须确定数组的大小,也就是确定开辟空间的边界!!!
引用数组元素:程序设计语言对数组类型提供了下标运算[]
:
指针常量/指针变量[整数]
等价于*(指针常量/指针变量 + 基类型长度*整数)
数组类型等价于指针变量基类型:规定了一个元素的长度、操作方式;
数组名类似于指针变量的值:数组名是一个静态地址,不可变;指针变量值存储一个地址,可变;
所以本质上,数组只是对指针操作进行了一些包装,都是利用指针的便利对数据进行访问。
int a[10];
int* p;
p = a;//指针变量指向了数组a的起始地址
for(int i = 0; i < 10; i++){
p[i] = i;
printf("%d ", a[i]);//输出0 1 2 3 4 5 6 7 8 9
}
在数组定义时,一定要确定数组的大小,在程序运行过程中批量数据的数量不能完全确定,想要开辟合适大小的内存空间,我们可以类比数组的方式,动态分配内存;
首先,定义一个指针变量用来存储起始地址;
然后,使用库函数malloc
分配空间,并将返回值赋值给第一步定义的指针变量;
最后,可以通过指针变量和下标操作访问所有的元素。
int* a;
a = (int*)malloc(sizeof(int)*n);//开辟大小为n的整型数组
for(int i = 0; i < n; i++){
a[i] = i;//可以为数组里每一个元素赋值
}
对这写库函数的使用可以查找资料,这里我们主要理解动态分配内存与数组的定义的相似性
动态分配的空间在读写操作上与定义的等大的数组没有区别,不同之处在于动态分配的指针变量可以变化指向新的空间,使用之后需要将分配的空间free
。
使用指针可以实现数组这种简单的数据结构,进一步如何实现更加复杂的数据结构呢?例如一个二维表
,表中的每一项数据的数据类型都是一致的,可以根据(行,列)定位到一个表项;
名词 | 说明 |
---|---|
char a[row][list] |
定义二维数组,在物理上是全体连续的 |
所占空间大小为row*(list*sizeof(char)) |
|
a |
静态地址,整个存储空间的起始地址 |
a[i] |
静态地址,并不存储在栈中,不是一个具体的存储单元 |
a[i][j] |
char型变量,对应一个一字节的存储单元 |
等价于*(a + i*(list*sizeof(cahr)) + j*sizeof(char)) |
名词 | 说明 |
---|---|
char *a[row] |
定义指针数组,数组a[row]是连续的 |
指针变量a[…]占空间地址长度*row |
|
a |
静态地址,存储指针的空间的起始地址 |
a[i] |
指针变量,需要动态分配空间,为其存储一个地址 |
a[i] = (char*)malloc(sizeof(char)*list); |
|
a[i][j] |
char型变量,对应一个一字节的存储单元 是和开辟的空间内多个元素连续的 |
名词 | 说明 |
---|---|
char **a |
定义双重指针, |
a |
指针变量,需要动态分配空间,为其存储一个地址 |
a = (char**)malloc(sizeof(char*)*row); |
|
a[i] |
指针变量,需要动态分配空间,为其存储一个地址 |
a[i] = (char*)malloc(sizeof(char)*list); |
|
a[i][j] |
字符型变量,对应一个一字节的存储单元 |
综上,可以将它们类比至多维的情况:
a[i][j]...
编程语言练习 / C语言