目录
1.指针
1.内存空间:
1.1CPU与内存协同工作:
CPU由三部分构成:
1.算数,逻辑单元:对数据执行运算的电路;
2.控制单元:协调机器活动的电路;
3.寄存器组:数据临时存储。
数据几乎都存储在内存上,仅有当前正在处理的数据,才放到寄存器组中,等待CPU计算。
接下来,我们看看两个数相加会经历哪些步骤:
1.从内存中取出一个加数放到寄存器中;
2.从内存中取出另一个加数,放到另一个寄存器中;
3.激活算术逻辑单元中的加法电路,将前面两步的寄存器作为输入,用另一个寄存器存放结果。
4.将结果存放到内存中。
1.2访问内存中的房间
内存才是数据的大本营,计算机通过晶体管的开关状态记录数据。他们通常为8个一组,我们称之为字节。既然内存需要存储数据,内存上必然有非常多的这种8个开关组成的编组。
我们可以把这种编组看作数据的房间,为了找到这些房间呢,每个房间都会有一个编号。第一个房间从0开始编号,以此类推。我们把房间号称之为内存地址.
1.3基础数据类型占内存大小
char—1;short—2;int—4;long—4;long long—8;float—4;double—8。
以int为例,我们有以下两种表达方式:
1.列举所有的房号:301,302,303,304;
2.首房间及房间数:从301开始的4个房间。
2.指针数据类型
2.1取地址运算符&
取地址运算符是一个一元运算符,写在一个数据对象左边,可以获取一个数据对象的首地址和所需存储空间大小。
int n;
类型 pn=&n;//获取数据对象n的首地址和所需所需空间大小
变量pn,存储了变量n的首地址和所需空间大小,那么通过变量pn可以在内存中找到变量n。
所以,变量pn到底是什么类型呢?
2.2声明指针类型的变量
int n;
int* pn=&n;
char c;
char* pc=&c;
声明指针的公式:目标类型*变量名
定义:设一个数据对象为x,设另一个数据对象为p。p存储了x的首地址和所占空间大小。那么p称之为x的指针,或者说p指向x。
2.3指针类型
首地址:指针类型的值就是目标数据对象的首地址;
空间大小:C语言通过不同的指针类型来标记目标数据对象的空间大小。
小结:指针类型通过值来保存目标数据对象的首地址,类型本身标记目标数据对象的空间大小。
3.使用指针
既然指针存储了一个数据对象的首地址与大小,并且通过这两个信息可以在内存中找到该数据对象,那么我们肯定可以使用指针来访问所指向的数据对象。
3.1取值运算符 *
取值运算符是一个一元运算符,写在一个指针的左边,可以根据指针中存储的首地址和空间大小找到目标数据对象。
虽然他和乘法运算符很像,但是,它是一个一元运算符,仅需要一个操作对象。
int n=123;
int *pn=&n;
printf("%u\n",pn); //打印n的首地址
printf("%d\n",*pn); //根据pn中的首地址与大小,找到的数据对象的值
除了通过指针访问所指向的数据对象,也可以通过指针修改所指向的数据对象。
#include <stdio.h>
int main()
{
int n = 0;
int* pn = &n;
char c = 0;
char* pc = &c;
//使用指针修改所指向数据对象
*pn = 123;
*pc = 'A';
//使用指针访问所指向数据对象
printf("n=%d\n", *pn);
printf("c=%c\n", *pc);
return 0;
}
3.2指针类型大小
char和int存储的是数据范围不同的两种数据。char存储小一点的整数范围,int存储大一点的整数范围。所以,char可以占用用小一点的空间,而int会占用大一点的空间。
但是char*和int*存储的均为数据对象地址,因此他们所占用的空间是相同的。
不同的编译器或编译配置可以让编译器生成32位或64位的程序,有时候被称作x86或x64.
1.32位程序可以访问0到2的32次方的地址范围;
2.64位程序可以访问0到2的64次方的地址范围。
3.3强制转换指针类型
int n=123;
int*pn=&n;
char*pc=(char*)pn;
虽然变量pn与pc不能在赋值时自动转换,但是,使用强制转换可以将pn转换为char*后赋值给pc。
2.指针运算
1.指针类型与整型进行加减
需要将整型转化为对应的指针进行运算,不然系统会报错。
并且指针类型加一后,将首地址向后挪了sizeof(目标数据对象)字节。
规律:sizeof(目标数据对象)被称作步长;
指针类型加n后,其首地址向后移动n*步长个字节。
指针类型减n后,其首地址向前移动n*步长个字节。
2.应用指针类型与整型加减
我们使用取值运算符可以将指针指向的目标数据对象进行修改。
例如*p=111,*(p-1)=222;
注意表达式p-1必须先被括号包裹,再使用取值运算符。因为取值运算符的优先级高于算术运算符。我们需要先将首地址移动在进行取值。若不使用括号那么*p会被先取值再加1.
请格外注意,函数内声明的变量在内存中不一定是首尾相接的,受到内存对其的影响,他们有可能不连续。这个例子仅仅是为了让读者理解指针与整形加减运算,请勿在实际使用中使用这种写法。
Levi·Ackerman:
请格外注意,函数内声明的变量在内存中不一定是首尾相接的,受到内存对其的影响,他们有可能不连续。这个例子仅仅是为了让读者理解指针与整形加减运算,请勿在实际使用中使用这种写法
另外,数组可以保证各元素在内存中首尾相接。各元素连续分布在内存上。所以C语言中可以使用指针与整形加减运算访问和修改数组元素。
4.同类型指针减法运算
规律:指针类型与指针类型相减,其结果为两首地址差值除以步长。
5.其他类型的指针运算
上面我们介绍了两种有指针类型参数的运算:
1.指针类型与整型加减。
2.同类型的指针相减。
它们的运算结果都在内存上拥有实际意义。
另外几种运算:
1.指针类型与整型进行乘除运算。
2.同类型的指针相加。
3.同类型指针乘除。
这些运算结果都没与实际意义,在C语言中无法通过编译。
3.指针与数组
3.1使用指针访问数组
指针类型的加减运算可以是指阵内保存的首地址移动。
例如:
int*p=(int*)100
p+1,结果为首地址向后移动sizeof(int)个字节,即104。
p-1,结果为首地址向前移动sizeof(int)个字节,即96。
因此,指针加减运算对于访问在内存中连续排布的数据对象非常方便。
3.2使用第一个元素获取数组首地址
既然数组元素在内存中的存储可以保证是连续的,那么第一个元素的首地址就是整个数组的首地址。我们可以使用取地址运算符&,获取第一个元素的首地址和空间大小,及获取一个int*类型的指针。
通过取值运算符*,可以使用指针中的首地址的空间大小访问或修改目标数据对象。
3.3使用数组名获取数组首地址
规则:设数组元素类型为T。
T arr[5]; //以T为元素数组arr
T *p //指向T的指针
类型为“以T为元素的数组arr”与“指向T的指针P”之间存在密切关系。
当数组名arr出现在一个表达式当中,数组名arr将会被转化为指向数组的一个元素的指针。但是这个规则有两个例外:
1.对数组名使用sizeof时;
2.对数组名使用&时。
3.4使用指针访问数组等价于下标访问
现在我们学会了访问数组元素的两种办法:
1.数组名【下标】
2.*(数组名+偏移量)
中括号[ ],被称作下标运算符,它的优先级高于其他一切运算符,通常形式为:
A[k]
而表达式运算时,最终会将下标运算符展开为:*(A+k)
4.指针作为参数传递
4.1将指针作为参数传递
由于在被调函数内部无法直接修改主调函数的变量。那么我们采用迂回战术,在函数main中取得a、b的指针。将两个指针传递到函数swap。那么,在函数swap内部可以根据这两个信息修改a、b。
注意:不是交换指针x、y的值,而是交换目标对象a、b的值。所以,需要在指针前使用取值运算符。
4.2指针不仅仅是首地址
再次强调,指针内保存的不仅仅是目标数据对象首地址,指针类型也非常重要。
4.3仅有首地址的指针类型void*
由于指针类型定死了指针所指向的数据类型,为了让函数可以交换更多的数据类型,我们仅需要指针类型中保存的首地址目标数据大小通过额外的参数传入。
int*修改为void*类型为void*的指针仅保存首地址,不保存目标数据对象的空间大小,所以不能对void*类型的指针进行取值,同样的,他也没有不长,所以不能对void*类型的指针进行加减运算。
但是viod*有一个好处,那就是任意类型的指针都可以直接赋值给他,而其他类型的指针是不能相互赋值的,由于赋值会改变目标数据对象的类型。
注意:
1.不同指针类型不能相互赋值,相互赋值后会造成目标数据对象类型的改变,无法通过编译。
2.viod*类型为特例,它可以接受任意指针类型的赋值,也可以赋值给任意类型指针。