理解C/C++中const char*、char* const、const char* const、char* const*等等
先说些题外话,今天学习execve(2)的使用,由于书上代码使用的是C89标准,所以下面这种代码都被我修改了
char* s[] = { "aaa", "bbb", "cc" };
也就是在char前面加个const,因为"aaa"、"bbb"、"cc"都是字符串字面值(string literal),在C++标准中string literal只能转换成const char*,原因是即使用char*指向string literal,也是无法修改的。比如上述代码不做修改在旧标准中是可行的,但是妄图用s[0][0] = 'd'来使s[0]变成"daa",那么运行时会报错,因为string literal是存在静态常量区的,不可修改,但是可以取得string literal的地址(用指针类型表示)。这就跟char*的语义产生了冲突,因为char*指向的是char而不是const char,理论上是可以赋值的。
于是当我改成const char* s[]后,传入execve(2)时编译报错:期待参数类型是char * const*,但是传入参数类型是const char **。
int execve(const char *filename, char *const argv[],
char *const envp[]);
当我去掉const(也就是变回了char* s[])后,编译通过。从例子可以看出,char**可以转换成char* const*,但是const char**不能显式转换成char* const*,这样的规则除了像我这样实际修改代码测试,还有什么办法记下来呢?
这个问题博客最后再回答,先出道题:以下类型的变量p,分别具有什么性质?
具体而言,p、p[0]、p[0][0]……是否可以修改(即而出p = q、p[0] = q这样的代码),像p[1]、p[2]……和p[0]类型是相同的,性质也相同,
(1)const char* p (2)char const* p (3)char* const p (4)const char** p (5)char const** (6)char* const* (7)char** const
思路简单来说就是“就近原则”+“符号*的消去”+“const性质”,具体看我下面解释
(1)const char* p:离p最近的是*而不是const,因此p可以修改;现在考虑p[0],那么,对一个指针p使用运算符*,操作步骤是消去*到p之间的所有部分,那么*p就变成了const char类型,被const修饰,因此不可以修改
验证代码
char s1[] = { 'a', 'b', 'c' };
char s2[] = { 'a', 'b', 'c' };
const char* p = s1;
p[] = 'd'; // 编译报错
p = s2; // 编译通过
(2)char const* p:离p最近的是*而不是const,因此p可以修改;考虑p[0],消去*后为char const,被const修饰,因此不可以修改。结果和(1)一样
(3)char* const p:离p最近的是const而不是*,因此p不可以修改;考虑p[0],消去* const部分剩下的是char,可以修改。
验证代码:把(1)的const char* p = s1;改成char* const p = s1;
(4)const char** p :离p最近的是*而不是const,因此p可以修改;考虑p[0],消去*后剩下const char*,距离最近的是*而不是const,因此p[0]可以修改;考虑p[0][0],消去*后剩下const char,不可修改;
验证代码
char s1[] = { 'a', 'b', 'c', '\0' };
char s2[] = { 'd', 'e', 'f', '\0' };
char* ss1[] = { &s1[], &s2[] };
char* ss2[] = { &s1[], &s2[] };
const char** p = (const char**)ss1; // 需要强制转换
p = (const char**)ss2; // 编译通过
p[] = &s2[]; // 编译通过
p[][] = 'x'; // 编译报错
(5)char const** p:同(4)
(6)char* const* p:离p最近的是*而不是const,因此p可以修改;考虑p[0],消去*后剩下char* const,距离最近的是const而不是*,因此p[0]不可修改;考虑p[0][0],消去* const后剩下char,可修改;
验证代码
char* const* p = ss1;
p = ss2; // 编译通过
p[] = &s1[]; // 编译报错
p[][] = 'x'; // 编译通过
(7)char** const p:离p最近的是const而不是*,因此p不可以修改;考虑p[0],消去* const后剩下char*,距离最近的是*,因此p[0]可以修改;考虑p[0][0],消去*后剩下char,可修改
验证代码
char** const p = ss1;
p = ss2; // 编译报错
p[] = &s1[]; // 编译通过
p[][] = 'x'; // 编译通过
总结下来,假设变量p,把p的类型看做字符串s(比如"const char*"),由于变量声明/定义时写作const char* p;因此把s最右边的看作当初距离p最近的
分析字符串s时忽略空格部分,规律如下:
(1)若s最右边的是const而不是*,则p不可修改;否则p可修改。
(2)p[0]的类型字符串只需要找到s最右边的字符*,然后去掉该字符以后(包括该字符)的部分,得到p[0]的类型字符串。
那么,再来解决之前的问题吧,为什么const char**不能显式转换成char* const*?
先来考虑char* const*的意义,设该类型的变量为p,用上述方法可得出:p可修改,p[0]不可修改,p[0][0]可修改。
而对const char** p而言:p可修改,p[0]可修改,p[0][0]不可修改。
把可修改的类型转换成不可修改的类型是安全的,但是把本身不可修改的类型转换成可修改的类型就是危险的。类型转换并不改变变量的本质,如果把一个不可修改的类型(比如之前提到的string literal)当成可修改的类型,试图修改,在代码中无法检测出来,但是运行时就会报错。但是把一个可修改的类型当成不可修改的类型,试图修改,编译时就会报错,并且这是我们期望的。
打个也许不太恰当的比方,现在眼前有个钻石,但是也有可能只是想块钻石的糖。
1、如果它是钻石,你把它当成糖,用力咬下去牙齿就碎了。
2、如果它是糖,你把它当成钻石,你就不会去咬,牙齿还是完好的,至少避免了不小心咬到钻石的危险。
理解C/C++中const char*、char* const、const char* const、char* const*等等的更多相关文章
- [Link 2005]vs2015 LNK2005 "class std::basic_ostream<char,struct std::char_traits<char> > & __cdecl printR(class std::basic_ostream<char,struct std::char_traits<char> > &,class QueryResult const &)" (?
vs2015 LNK2005 "class std::basic_ostream<char,struct std::char_traits<char> > &am ...
- C++中的const成员函数(函数声明后加const,或称常量成员函数)用法详解
http://blog.csdn.net/gmstart/article/details/7046140 在C++的类定义里面,可以看到类似下面的定义: 01 class List { 02 priv ...
- 深入理解C语言中的指针与数组之指针篇
转载于http://blog.csdn.net/hinyunsin/article/details/6662851 前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本 ...
- 深入理解C语言中的指针与数组之指针篇(转载)
前言 其实很早就想要写一篇关于指针和数组的文章,毕竟可以认为这是C语言的根本所在.相信,任意一家公司如果想要考察一个人对C语言的理解,指针和数组绝对是必考的一部分. 但是之前一方面之前一直在忙各种事情 ...
- 理解C语言中几个常见修饰符
写在前面 今天下午一个同事问「register」关键字是什么作用?噢,你说的是「register」啊,它的作用是……脑袋突然断片儿,我擦,啥意思来着,这么熟悉的陌生感.做C语言开发时间也不短了,不过好 ...
- [php-src]理解Php内核中的函数与INI
内容均以php-5.6.14为例. 一. 函数结构 内核中定义一个php函数使用 PHP_FUNCTION 宏 包装,扩展也不例外,该宏在 ./main/php.h:343 有着一系列类似以 PHP ...
- char 转wchar_t 及wchar_t转char
利用WideCharToMultiByte函数来转换,该函数映射一个unicode字符串到一个多字节字符串.通常适合于window平台上使用. #include <tchar.h> #in ...
- 【转载】理解C语言中的关键字extern
原文:理解C语言中的关键字extern 最近写了一段C程序,编译时出现变量重复定义的错误,自己查看没发现错误.使用Google发现,自己对extern理解不透彻,我搜到了这篇文章,写得不错.我拙劣的翻 ...
- 深入理解Java虚拟机--中
深入理解Java虚拟机--中 第6章 类文件结构 6.2 无关性的基石 无关性的基石:有许多可以运行在各种不同平台上的虚拟机,这些虚拟机都可以载入和执行同一种平台无关的字节码(ByteCode),从而 ...
随机推荐
- js中字符串与数组的相互转换
<!doctype html> <html> <head> <meta charset="utf-8"> <title> ...
- linux oracle11g 数据 导入到10g数据库
说明: 原用户名和密码:test/test 目标用户名和密码:test01/test 11G 服务器: 1.创建dmp文件存储目录 # mkdir -p /tmp/backup # sqlplus ...
- poj2771
题解: 二分图最大独立及 每两个不能选的渐变 输出n+m-最大匹配 代码: #include<cstdio> #include<cmath> #include<algor ...
- CSS3 文本超出后显示省略号...
纯用CSS实现,主要采用代码 overflow:hidden; text-overflow:ellipsis;//这是让文本溢出后,显示成省略号. white-space:nowrap;//禁止自动换 ...
- SpringXML方式配置bean的自动装配autowire
Spring的自动装配,也就是定义bean的时候让spring自动帮你匹配到所需的bean,而不需要我们自己指定了. 例如: User实体类里面有一个属性role 1 2 3 4 5 6 7 publ ...
- 修复 海盗船 k70 lux 未检测到设备(k70 no device detected)
corsair k70 lux 上周收到的生日礼物,头一次用机械键盘,还是这么高端的机械键盘(729RMB),手感一级棒.但是,有问题啊!把键盘上的 bios按钮拨到8上电脑可以识别,scroll 灯 ...
- How to choose the number oftopics/partitions in a Kafka cluster?
How to choose the number oftopics/partitions in a Kafka cluster? 如何为一个kafka集群选择topics/partitions的数量? ...
- ffmpeg+EasyDSS流媒体服务器实现稳定的rtmp推流直播
本文转自EasyDarwin团队成员Alex的博客:http://blog.csdn.net/cai6811376/article/details/74783269 需求 在做EasyDSS开发时,总 ...
- HAWQ + MADlib 玩转数据挖掘之(四)——低秩矩阵分解实现推荐算法
一.潜在因子(Latent Factor)推荐算法 本算法整理自知乎上的回答@nick lee.应用领域:"网易云音乐歌单个性化推荐"."豆瓣电台音乐推荐"等. ...
- Jmeter查看结果树
取样结果: Thread Name: test 1-2 线程名称:测试1 - 2Sample S ...