了解指针前,首先要明确内存的定义:内存是电脑上重要的存储器,计算机中所有程序的运行都是在内存中进行的。
所以为了有效地使用内存,大多数现代计算机都把内存分割成一个个小的内存单元。
而为了能够有效地访问到内存的每个单元,计算机又给内存单元进行了编号,以与其他内存单元进行区分,这些编号就被称为该内存单元的地址(address)。
此时就引出两个问题:
- 内存是怎么编号的?
答:计算机的32位和64位实际上指的是该计算机地址线的根数,它们的通电与否分别代表1和0,这就是把电信号转换成数字信息——二进制序列。以32位为例,其能够产生的如总共有2的32次方个的序列,就可以作为内存单元的编号(地址)。如下:
00000000000000000000000000000000
...
00000000000000001111111111111111
...
11111111111111111111111111111111
2.一个内存单元是多大空间?答:每个内存单元的大小是1个字节,即8个比特。
在C语言中添加数据(如变量)时,会给对象分配空间(内存单元),而想要访问这个对象的地址,就需要利用取地址运算符&
获取其地址——第一个字节的地址。示例:
int a = 10; //a在内存中要分配空间,int类型为4个字节
printf("%p\n", &a); //%p 是专门用来打印地址的
int* pa = &a;
这段代码中的变量pa
是用来存放地址的,在C语言中被称为指针变量(pointer variable),也可以叫指针(pointer)。
(但指针实际上是一个抽象概念,大部分情况下既可以指指针变量,也可以指地址,如果一时无法理解,最好在使用中逐渐领会,而不要强行下定义。)
对于这个指针变量,我们会说:pa
“指向”a
。
声明它时int
后的*
说明其是指针变量,而int
说明pa
指向的对象是int
类型的。
指针变量的声明可以和其他变量一起进行:
int a, b, arr[5], * p;
并且,指针是可以传递的,将上面的pa
的值赋给另一个指针变量,它们将同时指向a
:
int* pb;
pb = pa;
获取地址后就能够通过解引用操作符*
,来访问地址,对该对象进行操作:
int a = 10;
int* pa = &a;
*pa = 20; //*pa就是通过存储在pa里面的地址找到a
printf("%d\n", a); //20
因为pa
指向a
,所以也可以说*pa
是a
的别名。
*pa
不仅值与a
相同,实际上本质也相同。对*pa
的改变也会产生对a
的改变。
其实倒不如说,改变*pa
就是改变a
,这一点要千万注意。
而指针变量本身的大小是多少呢?
printf("%d\n", sizeof(char*)); //4 或 8
printf("%d\n", sizeof(short*)); //4 或 8
printf("%d\n", sizeof(int*)); //4 或 8
printf("%d\n", sizeof(long*)); //4 或 8
printf("%d\n", sizeof(long long*)); //4 或 8
printf("%d\n", sizeof(float*)); //4 或 8
printf("%d\n", sizeof(double*)); //4 或 8
实际上,指针的大小是相同的,因为指针是用来存放地址的,指针需要多大空间,取决于地址的存储需要多大空间——即4或8取决于是32位平台还是64位平台。
结构体(Struct)也叫结构,是C语言的重要知识点,它使得C语言有能力描述复杂类型。
如果有学过JavaScript或其他某些语言,会发现结构体与对象、记录等的概念十分相似,它是可能具有不同类型的值的集合,这些值被称为结构体的成员。
当我们需要存储一组相关数据项,而且这些数据的类型不完全相同时,结构体是理想的选择。
简单示例:
//模拟一个学生的简单结构体,Stu是student的缩写
struct Stu
{
//成员变量
char name[20];
int age;
double score;
};
int main()
{
struct Stu s = { "张三", 20, 85.5 }; //结构体的创建和初始化
//读取方式1:结构体变量.成员变量
printf("1:%s %d %lf\n", s.name, s.age, s.score);
struct Stu* ps = &s; //结构体类型的指针
//读取方式2:结构体指针解引用.成员变量名
printf("2:%s %d %lf\n", (*ps).name, (*ps).age, (*ps).score);
//读取方式3:结构体指针->成员变量名
printf("3:%s %d %lf\n", ps->name, ps->age, ps->score);
//以上三种读取方式的结果都是 张三 20 85.500000
return 0;
}
注:每个结构体代表一个新的作用域,因此在这个作用域里声明的名字都不会和程序中的其他名字冲突。C语言表述为:每个结构体都为它的成员设置了独立的命名空间(name space)。