理解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),从而 ...
随机推荐
- Truncate a string
用瑞兹来截断对面的退路! 截断一个字符串! 如果字符串的长度比指定的参数num长,则把多余的部分用...来表示. 切记,插入到字符串尾部的三个点号也会计入字符串的长度. 但是,如果指定的参数num小于 ...
- NEU 1495 a interesting game 大数 难度:1
问题 G: a interesting game 时间限制: 1 Sec 内存限制: 128 MB提交: 29 解决: 10[提交][状态][讨论版] 题目描述 One day,Kid is in ...
- gzip压缩解压缩
压缩/解压缩压缩/解压缩之后的文件名称 必须是gz 解压缩
- Linux系统在启动过程中启动级别发生错误的解决办法
一.系统启动级别一共有六个: 0:系统停机模式,系统不可以正常启动 1:单用户模式, root权限,用于系统的维护,禁止远程登陆 2:多用户模式,没有NFS网络支持 3:完整的多用户文本模式,有NFS ...
- ajaxFileUpload的data数据带pre标签
解决办法: var ret = jQuery.parseJSON(jQuery(data).text());
- DIV css中cursor属性详解-鼠标移到图片变换鼠标形状 (转)
css中cursor属性详解-鼠标移到图片变换鼠标形状 语法: cursor : auto | all-scroll | col-resize| crosshair | default | han ...
- == 和 equals 的用法
在java中,boolean.byte.short.int.long.char.float.double这八种是基本数据类型,其余都是引用类型. “==”是比较两个变量的值是否相等, “equals” ...
- EasyDSS流媒体服务器Linux emerg getpwnam("xxx") failed解决办法
本文转自EasyDarwin开源团队Alex的博客:http://blog.csdn.net/cai6811376/article/details/73770943 EasyDSS 流媒体服务器是什么 ...
- Java 进阶巩固:什么是注解以及运行时注解的使用
这篇文章 2016年12月13日星期二 就写完了,当时想着等写完另外一篇关于自定义注解的一起发.结果没想到这一等就是半年多 - -. 有时候的确是这样啊,总想着等条件更好了再干,等准备完全了再开始,结 ...
- [Linux] jq:命令行JSON处理工具
jq命令帮助我们很方便地在终端查看和处理json文件 jq命令的帮助信息: abby@abby:bgs$ jq -h jq - commandline JSON processor [version ...