1.基本概念
1.1 存储单元
存储单元一般应具有存储数据和读写数据的功能,以8位(bit)二进制作为一个存储单元,也就是一个字节。
每个单元有一个地址,是一个整数编码,可以表示为二进制整数。程序中的变量和主存储器的存储单元相对应。
变量的地址对应着存储单元的地址,变量内容对应着单元所存储的数据。
存储地址一般用十六进制数表示,而每一个存储器地址中又存放着一组二进制(或十六进制)表示的数,通常称为该地址的内容。
变量有三个要素是:变量名、变量类型、变量值。
变量代表了一个存储单元,其中的值是可变的,故称为变量。
1.2 C语言的变量
变量a 本质上代表一个存储单元。CPU通过该存储单元的地址访问该存储单元中的数据。所以a本来代表两个值:存储单元的地址和储单元中的数据。于是就有了二异性。
为了消除这种二义性,C语言规定a表示存储单元中的数据,&a表示存储单元的地址。
a存储单元中的数据可以是一个普通数值,也可以是另一个存储单元的地址,比如:
a = &b;
语句就是将b的存储单元的地址存入a存储单元中。
C语言规定*a代表a中存储的地址对应的存储单元中的数据,也就是访问*a就等于访问b,于是*a提供了通过a访问b中的数据的手段。
1.3 操作符 *、&
*:取某地址的值(数据内容),运算符后通常跟一个地址
&:取某数据(变量)的地址,运算符后通常跟一个变量
- a表示a对应的存储单元中的数据。
- &a表示a对应的存储单元的地址。
- *a表示:首先,要求a对应的存储单元中的数据一定是另一个存储单元的地址。
- 于是,*a表示另一个存储单元中的数据。
当a声明的类型是int时,a中存储的是一个整数数值,通过a可以访问(读取或修改)这个数值。
当a声明的类型是int *时,a中存储的是一个存储单元的地址,而该存储单元中存储的数据是一个整数数值;
通过*a可以访问(读取或修改)这个数值。a == &a 都是该存储单元的地址。
当a声明的类型是int *时,a中存储的是一个存储单元的地址,而该存储单元中存储的数据是另外一个存储单元的地址,另外这个存储单元中存储的是一个整数数值;通过**a可以访问(读取或修改)这个数值。
1.4 指针
C语言中,地址也称指针。
计算机中所有数据都必须放在内存中,不同类型的数据所占的字节数不一样,如int型占用4字节,char占用一个字节。
我们将内存中字节的编号称为地址(address)或指针(pointer),地址从0开始依次增加。
1.4.1 指针的大小
指针是一个形无符号整型,一个整数,它的大小取决于系统是16 32 还是64位的 16/8=2byte 32/8=4byte 64/8=8byte.
1.4.2 指针指向的内容的大小
指针所指向的内存块所占内存大小.32位编译环境下,char占一个字节,int占2个字节,double占4个字节,long double占8个字节(这是默认的32位编译环境下,在64位下翻倍就是了),所以一个char指针所占内存为4个字节(32位下),所指向的内存区域占1个字节.同理其它类型也是一样的。
2.具体解析
2.1 普通变量(不带*)
int i = 100;
此时,普通变量变量名i即地址中的数据(变量值),也就是100。存在一个地址:&i(表示唯一)。
简单理解:普通变量名=数据。
#include <stdio.h>
void test () {
int i = 100;
printf("i-----%i\n",i);
printf("&i----%p\n",&i);
}
int main(int argc, const char * argv[]) {
test();
return 0;
}
控制台输出
i-----100
&i----0x7ffeefbff51c
2.2 一级指针变量
int *a = &i;
一级指针变量:变量名a实际所指的内容为变量i的地址,即a=&i=&(*a)=*(&a);其中*a表示通过a中的内容(i的地址)取值即i。存在两个地址:&a(唯一表示方式)和a(表示方式不唯一,如上)。
简单理解:指针变量名=地址-------->数据。
#include <stdio.h>
void test () {
int i = 100;
printf("i-----%i\n",i);
printf("&i----%p\n",&i);
int *a;
printf("a-----%p\n",a);
printf("&a----%p\n",&a);
a = &i;
printf("a-----%p\n",a);
}
int main(int argc, const char * argv[]) {
test();
return 0;
}
控制台输出
i-----100
&i----0x7ffeefbff51c
a-----0x0
&a----0x7ffeefbff510
a-----0x7ffeefbff51c
Program ended with exit code: 0
变量a声明后没有赋值,其值为0,将&i(也就是i的地址)赋值给a后,a保存了i的地址,这是变量a对应的存储单元存储的数据,而a本身有自己的地址即自身存储单元的地址。(这里说存储单元并不是特别准确,应该是以a地址开头的存储块)
2.3 二级指针变量
int **p = &a;
二级指针变量:变量名p所指内容为一级指针的地址=&a=&(*p)=*(&p)=p,该地址(&(*p))中的内容是*p,其中*p中的内容为i的地址=&i=&(**p)=**(&p)=*p
(其中p=&a,a=&i)。
存在三个地址:&p(表示唯一)、p(表示不唯一)、*p(表示不唯一)。
简单理解:指针变量名=地址---------->地址------------>数据。
其中&(*)和*(&)相互抵消功能。
#include <stdio.h>
void test () {
int i = 100;
printf("i-----%i\n",i);
printf("&i----%p\n",&i);
int *a;
printf("a-----%p\n",a);
printf("&a----%p\n",&a);
a = &i;
printf("a-----%p\n",a);
int **p;
p = &a;
printf("p-----%p\n",p);
printf("*p----%p\n",*p);
printf("&p-----%p\n",&p);
}
int main(int argc, const char * argv[]) {
test();
return 0;
}
控制台输出
i-----100
&i----0x7ffeefbff51c
a-----0x0
&a----0x7ffeefbff510
a-----0x7ffeefbff51c
p-----0x7ffeefbff510
*p----0x7ffeefbff51c
&p-----0x7ffeefbff508
Program ended with exit code: 0
你可以尝试自己分析一下。
二级指针保存的是一级指针的地址,它的类型是指针变量,而一级指针保存的是指向数据所在的内存单元的地址,虽然都是地址,但是类型是不一样的。
3.指针与数组
C语言中由于指针的灵活性,导致指针能代替数组使用,或者混合使用。容易混淆的是字符数组和字符指针这两个,下面就这两个进行解析。
3.1字符数组
char str[10] = {"hello world"};
当编译这句代码时,编译器会将str数组中的元素从第一个元素开始逐个填入(hello world\0 )。
由于C语言中没有真正的字符串类型,可以通过字符数组表示字符串,因为它的元素地址是连续的。
C语言中规定数组代表数组所在内存位置的首地址,也是 str[0]的地址,即str = &str[0];
让我们来看一个问题
printf("%s",str);
为什么用首地址就可以输出字符串。
在C语言中字符串常量的本质表示其实是一个地址。
3.2 字符指针
char *s ;
s = "Hello";
为什么可以把一个字符串赋给一个指针变量。。
这不是类型不一致吗???
C语言中编译器会给字符串常量分配地址,如果 "Hello", 存储在内存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .
s = "Hello" ,这是什么操作,对了,地址。
其实真正的意义是 s ="Hello" = 0x3000;
看清楚了吧 ,Hello 看作是字符串,但是编译器把它看作是地址 0x3000,即字符串常量的本质表现是代表它的第一个字符的地址。。。。。。。。。。
s = 0x3000
那么 %s ,它的原理其实也是通过字符串首地址输出字符串,printf("%s ", s); 传给它的其实是s所保存的字符串的地址。。。
字符数组:
char str[10] = "hello";
前面已经说了,str = &str[0] , 也等于 "hello"的首地址。。
所以printf("%s",str); 本质也是 printf("%s", 地址");
3.3 char * 与 char a[];
char *s;
char a[] ;
前面说到 a代表字符串的首地址,而s这个指针也保存字符串的地址(其实首地址),即第一个字符的地址,这个地址单元中的数据是一个字符,
这也与 s 所指向的 char 一致。
因此可以 s = a;
但是不能 a = s;
C语言中数组名可以复制给指针表示地址, 但是却不能赋给给数组名,它是一个常量类型,所以不能修改。。
3.4 char ** 与char * a[]
char *a [] ;
由于[] 的优先级高于* 所以a先和 []结合,他还是一个数组,数组中的元素才是char * ,前面讲到char * 是一个变量,保存的地址。。
所以 char *a[ ] = {"China","French","America","German"};
同过这句可以看到, 数组中的元素是字符串,那么sizeof(a) 是多少呢,有人会想到是五个单词的占内存中的全部字节数 6+7+8+7 = 28;
但是其实sizeof(a) = 16;
为什么,前面已经说到, 字符串常量的本质是地址,a 数组中的元素为char * 指针,指针变量占四个字节(64bit编译器为8个字节),那么四个元素就是16个字节了
3.4.1 易错的char **
char **s = "hello world";
s的类型是 char ** 而 "hello world "的类型是 char *
虽然都是地址,但是指向的类型不一样,因此,不能这样用。从其本质来分析,"hello world",代表一个地址,比如0x003001,这个地址中的内容是 'h',为 char 型,而 s 也保存一个地址 ,这个地址中的内容(*s) 是char * ,是一个指针类型,所以两者类型是不一样的。
3.4.2 char **崩溃
char **s;
*s = "hello world";
上面的代码貌似是合理的,编译也没有问题,但是 printf("%s",*s),就会崩溃
why??
咱来慢慢推敲一下。。
printf("%s",*s); 时,首先得有s 保存的地址,再在这个地址中找到 char *的地址,即*s;
举例:
s = 0x1000;
在0x1000所在的内存单元中保存了"hello world"的地址 0x003001 , *s = 0x003001;
这样printf("%s",*s);
这样会先找到 0x1000,然后找到0x003001;
如果直接 char **s;
*s = "hello world";
s 变量中保存的是一个无效随机不可用的地址, 谁也不知道它指向哪里。*s 操作会崩溃。。
所以用 char **s 时,要给它分配一个内存地址。
char **s ;
s = (char **) malloc(sizeof(char**));
*s = "hello world";
这样 s 给分配了了一个可用的地址,比如 s = 0x412f;
然后在 0x412f所在的内存中的位置,保存 "hello world"的值。
END