深入 理解char * ,char ** ,char a[ ] ,char *a[] 的区别(转)
C语言中由于指针的灵活性,导致指针能代替数组使用,或者混合使用,这些导致了许多指针和数组的迷惑,因此,刻意再次深入探究了指针和数组这玩意儿,其他类型的数组比较简单,容易混淆的是字符数组和字符指针这两个。。。下面就开始剖析一下这两位的恩怨情仇。。。
1 数组的本质
数组是多个元素的集合,在内存中分布在地址相连的单元中,所以可以通过其下标访问不同单元的元素。。
2 指针。
指针也是一种变量,只不过它的内存单元中保存的是一个标识其他位置的地址。。由于地址也是整数,在32位平台下,指针默认为32位。。
3 指针的指向?
指向的直接意思就是指针变量所保存的其他的地址单元中所存放的数据类型。
int * p ;//p 变量保存的地址所在内存单元中的数据类型为整型
float *q;// ........................................浮点型
不论指向的数据类型为那种,指针变量其本身永远为整型,因为它保存的地址。
4 字符数组。。。
字面意思是数组,数组中的元素是字符。。确实,这就是它的本质意义。
char str[10];
定义了一个有十个元素的数组,元素类型为字符。
C语言中定义一个变量时可以初始化。
char str[10] = {"hello world"};
当编译器遇到这句时,会把str数组中从第一个元素把hello world\0 逐个填入。。
由于C语言中没有真正的字符串类型,可以通过字符数组表示字符串,因为它的元素地址是连续的,这就足够了。
C语言中规定数组代表数组所在内存位置的首地址,也是 str[0]的地址,即str = &str[0];
而printf("%s",str); 为什么用首地址就可以输出字符串。。
因为还有一个关键,在C语言中字符串常量的本质表示其实是一个地址,这是许多初学者比较难理解的问题。。。
举例:
char *s ;
s = "China";
为什么可以把一个字符串赋给一个指针变量。。
这不是类型不一致吗???
这就是上面提到的关键 。。
C语言中编译器会给字符串常量分配地址,如果 "China", 存储在内存中的 0x3000 0x3001 0x3002 0x3003 0x3004 0x3005 .
s = "China" ,意识是什么,对了,地址。
其实真正的意义是 s ="China" = 0x3000;
看清楚了吧 ,你把China 看作是字符串,但是编译器把它看作是地址 0x3000,即字符串常量的本质表现是代表它的第一个字符的地址。。。。。。。。。。
s = 0x3000
这样写似乎更符合直观的意思。。。
搞清楚这个问题。。
那么 %s ,它的原理其实也是通过字符串首地址输出字符串,printf("%s ", s); 传给它的其实是s所保存的字符串的地址。。。
比如
#include <stdio.h>
int main()
{
char *s;
s = "hello";
printf("%p\n",s);
return 0;
}
可以看到 s = 0x00422020 ,这也是"China"的首地址
所以,printf("%s",0x00422020);也是等效的。。
字符数组:
char str[10] = "hello";
前面已经说了,str = &str[0] , 也等于 "hello"的首地址。。
所以printf("%s",str); 本质也是 printf("%s", 地址");
C语言中操作字符串是通过它在内存中的存储单元的首地址进行的,这是字符串的终极本质。。。
5 char * 与 char a[ ];
char *s;
char a[ ] ;
前面说到 a代表字符串的首地址,而s 这个指针也保存字符串的地址(其实首地址),即第一个字符的地址,这个地址单元中的数据是一个字符,
这也与 s 所指向的 char 一致。
因此可以 s = a;
但是不能 a = s;
C语言中数组名可以复制给指针表示地址, 但是却不能赋给给数组名,它是一个常量类型,所以不能修改。。
当然也可以这样:
char a [ ] = "hello";
char *s =a;
for(int i= 0; i < strlen(a) ; i++)
printf("%c", s[i]);
或 printf("%c",*s++);
字符指针可以用 间接操作符 *取其内容,也可以用数组的下标形式 [ ],数组名也可以用 *操作,因为它本身表示一个地址 。。
比如 printf("%c",*a); 将会打印出 'h'
char * 与 char a[ ] 的本质区别:
当定义 char a[10 ] 时,编译器会给数组分配十个单元,每个单元的数据类型为字符。。
而定义 char *s 时, 这是个指针变量,只占四个字节,32位,用来保存一个地址。。
sizeof(a) = 10 ;
sizeof(s) = ?
当然是4了,编译器分配4个字节32位的空间,这个空间中将要保存地址。。。
printf("%p",s);
这个表示 s 的单元中所保存的地址。。
printf("%p",&s);
这个表示变量本身所在内存单元地址。。。。,不要搞混了。。
用一句话来概括,就是 char *s 只是一个保存字符串首地址的指针变量, char a[ ] 是许多连续的内存单元,单元中的元素为char ,之所以用 char *能达到
char a [ ] 的效果,还是字符串的本质,地址,即给你一个字符串地址,便可以随心所欲的操所他。。但是,char* 和 char a[ ] 的本质属性是不一样的。。
6 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 * 指针,指针变量占四个字节,那么四个元素就是16个字节了
看一下实例:
#include <stdio.h>
int main()
{
char *a [ ] = {"China","French","America","German"};
printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]);
return 0;
}
可以看到数组中的四个元素保存了四个内存地址,这四个地址中就代表了四个字符串的首地址,而不是字符串本身。。。
因此sizeof(a)当然是16了。。
注意这四个地址是不连续的,它是编译器为"China","French","America","German" 分配的内存空间的地址, 所以,四个地址没有关联。
#include <stdio.h>
int main()
{
char *a [ ] = {"China","French","America","German"};
printf("%p %p %p %p\n",a[0],a[1],a[2],a[3]); //数组元素中保存的地址
printf("%p %p %p %p\n",&a[0],&a[1],&a[2],&a[3]);//数组元素单元本身的地址
return 0;
}
可以看到 0012FF38 0012FF3C 0012FF40 0012FF44,这四个是元素单元所在的地址,每个地址相差四个字节,这是由于每个元素是一个指针变量占四个字节。。。
char **s;
char **为二级指针, s保存一级指针 char *的地址,关于二级指针就在这里不详细讨论了 ,简单的说一下二级指针的易错点。
举例:
char *a [ ] = {"China","French","America","German"};
char **s = a;
为什么能把 a赋给s,因为数组名a代表数组元素内存单元的首地址,即 a = &a[0] = 0012FF38;
而 0x12FF38即 a[0]中保存的又是 00422FB8 ,这个地址, 00422FB8为字符串"China"的首地址。
即 *s = 00422FB8 = "China";
这样便可以通过s 操作 a 中的数据
printf("%s",*s);
printf("%s",a[0]);
printf("%s",*a);
都是一样的。。。
但还是要注意,不能a = s,前面已经说到,a 是一个常量。。
再看一个易错的点:
char **s = "hello world";
这样是错误的,
因为 s 的类型是 char ** 而 "hello world "的类型是 char *
虽然都是地址, 但是指向的类型不一样,因此,不能这样用。,从其本质来分析,"hello world",代表一个地址,比如0x003001,这个地址中的内容是 'h'
,为 char 型,而 s 也保存一个地址 ,这个地址中的内容(*s) 是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"的值。。
再如:
#include <stdio.h>
void buf( char **s)
{
*s = "message";
}
int main()
{
char *s ;
buf(&s);
printf("%s\n",s);
}
二级指针的简单用法。。。。,说白了,二级指针保存的是一级指针的地址,它的类型是指针变量,而一级指针保存的是指向数据所在的内存单元的地址,虽然都是地址,但是类型是不一样的。。。
深入 理解char * ,char ** ,char a[ ] ,char *a[] 的区别(转)的更多相关文章
- 【转】深入理解const char*p,char const*p,char *const p,const char **p,char const**p,char *const*p,char**const p
一.可能的组合: (1)const char*p (2)char const*p (3)char *const p(4)const char **p (5)char const**p (6)char ...
- 深入理解const char*p,char const*p,char *const p,const char **p,char const**p,char *const*p,char**const p
由于没有const*运算,const实际上修饰的是前面的char*,但不能在定义时转换写成 const(char *)*p,因为在定义是"()"是表示函数. 三.深入理解7种组合 ...
- 个人理解的int数组和char数组
char数组中不论是一维还是二维的,在程序执行时每一块的分离依据都是以提供的起始地址到'\0'为一个处理的字符串.所以关于char[]的函数都是只提供相应起始地址作为形参就可以. char[]互相交换 ...
- 理解C/C++中const char*、char* const、const char* const、char* const*等等
先说些题外话,今天学习execve(2)的使用,由于书上代码使用的是C89标准,所以下面这种代码都被我修改了 char* s[] = { "aaa", "bbb" ...
- 答:SQLServer DBA 三十问之一: char、varchar、nvarchar之间的区别(包括用途和空间占用);xml类型查找某个节点的数据有哪些方法,哪个效率高;使用存储 过程和使用T-SQL查询数据有啥不一样;
http://www.cnblogs.com/fygh/archive/2011/10/18/2216166.html 1. char.varchar.nvarchar之间的区别(包括用途和空间占用) ...
- C语言中为什么不能把char**赋给const char**
这是我在知乎回答的一个问题. 这个问题是C中的一个深坑,首先说结论: char ** 和 const char ** 是两个不相容(incompatible)的类型,能够理解为不能直接赋值 在C11的 ...
- char *p="abc" 与 char p[]="abc" 的区别
本文来源于网络 出处:点我 有这样一段代码: #include "stdio.h" char *get_string_1() { char p[] = "hello wo ...
- unicode下各种类型转换,CString,string,char*,int,char[]
把最近用到的各种unicode下类型转换总结了一下,今后遇到其他的再补充: 1.string转CString string a=”abc”; CString str=CString(a.c_str() ...
- C# byte[]与char[]、string与char[]、byte[] 与 string 互转
1. byte array -> char array Byte[] b=new byte[5]{0x01,0x02,0x03,0x04,0x05}; Char[] c=Encoding.AS ...
- char s[]字串和char *s字串有什麼区别?
C語言有兩種字串宣告方式char s[]和char *s,兩者有什麼差異呢? Introduction char s[] = "Hello World"; (只是用字符串常量初始化 ...
随机推荐
- JAVA中的变量及取值范围
字节是二进制数据的单位.一个字节通常8位长.但是,一些老型号计算机结构使用不同的长度.为了避免混乱,在大多数国际文献中,使用词代替byte.变量: 变量的数据类型:变量名=变量值 数据类型 基本型 数 ...
- MeteoInfoLab脚本示例:AVHRR HDF数据
这里演示读取和绘制AVHRR hdf格式数据,以sst(海表面温度)为例. 脚本程序: #Add data file f = addfile('D:/Temp/hdf/2006001-2006005. ...
- pytest框架: fixture之conftest.py
原文地址:https://blog.csdn.net/BearStarX/article/details/101000516 一.fixture优势1.fixture相对于setup和teardown ...
- ES异常处理-NoNodeAvailableException
1.问题描述 ES client客户端能创建,但是在用客户端操作时报:NoNodeAvailableException[None of the configured nodes are availab ...
- 【状态压缩DP】HDU 4352 XHXJ'S LIS
题目大意 Vjudge链接 定义一个数的内部LIS长度表示这个数每个数位构成的序列的LIS长度,给出区间\([l,r]\),求区间内内部LIS长度为\(k\)的数的个数. 输入格式 第一行给出数据组数 ...
- centos8上安装mysql8
一,下载并解压mysql8 1,mysql官网 https://www.mysql.com/ 2,下载到source目录 [root@yjweb source]# wget https://cdn.m ...
- centos8上配置openresty/nginx可访问php
一,创建一个测试站的目录 [root@yjweb data]# mkdir dev [root@yjweb data]# cd dev [root@yjweb dev]# mkdir think_ww ...
- 【事件中心 Azure Event Hub】关于EventHub中出现Error时候的一些问题(偶发错误,EventHub后台升级,用户端错误,Retry机制的重要性)
请问对偶发的定义是多少频率? 针对偶发的定义,主要是看发生的时间非常短,次数极少(如 10次以内),并且发生的时候EventHub其他分区或其他连接都是正常接收和发送数据.所以对于频率是没有明确的定义 ...
- docker gitlab搭建
1,官方查找gitlab docker镜像 2,pull镜像 3,run docker run -d -p 443:443 -p 10080:80 -p 22:22 --name gitlab --r ...
- Qlik Sense插件及QRS接口补充
date: 2019-10-18 09:10:00 updated: 2019-10-18 15:18:00 Qlik Sense插件及QRS接口补充 1.插件 1.1 获取数据方式 理论上 Engi ...