C语言关于指针的 a、*a、&a

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

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,194评论 6 490
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,058评论 2 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 156,780评论 0 346
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,388评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,430评论 5 384
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,764评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,907评论 3 406
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,679评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,122评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,459评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,605评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,270评论 4 329
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,867评论 3 312
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,734评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,961评论 1 265
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,297评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,472评论 2 348

推荐阅读更多精彩内容