理解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),从而 ...
随机推荐
- [转载]Java读取Excel中的单元格数据
目前网上能找到的读取Excel表格中数据的两种比较好的方案:PageOffice好用开发效率高:POI免费.供大家参考,针对具体情况选择具体方案. 1. PageOffice读取excel impor ...
- 部署C# ReportViewer遇到的坑
前些天临时给客户做个工具,统计具体时间点各种车型数据的数量及比重,为了显示方便就用C#来做,因为它有现成的reportviwer控件提供了显示,打印,导出功能.原本我以为这个控件是.netframew ...
- Yii ExtendedActiveRecord 增强版 ActiveRecord 增加多数据库连接绑定功能
ExtendedActiveRecord 继承自 CActiveRecord,因此基础功能与 CActiveRecord 无异 为添加对多数据库连接的支持,增加了对 connectionName() ...
- JQuery iframe
子页面获取父页面的元素 function colisetapTJ() { var tapid = $('div:contains("添加档案报送"):last', window.p ...
- sql中exists,Intersect ,union 与union All的用法
熟练使用SQL Server中的各种用法会给查询带来很多方便.今天就介绍一下EXCEPT和INTERSECT.注意此语法仅在SQL Server 2005及以上版本支持. EXCEPT是指在第一个集合 ...
- 【Html 学习笔记】第七节——表单
文本框:<form> <input> </form> 密码域-文本框:<input type ="password" > 复选框:& ...
- about system (pause) in cpp
Which is best way to pause the console in C++ programs? using cin.get() or using system("pause& ...
- RxJava 1.x 笔记:过滤型操作符
我真的是奇怪,上下班的路上看书.看文章学习的劲头特别大,到了周末有大把的学习时间,反而不珍惜,总想打游戏,睡前才踏踏实实地写了篇文章,真是服了自己! 本文内容为 RxJava 官方文档 学习笔记 作者 ...
- 深度学习(六十八)darknet使用
这几天因为要对yolo进行重新训练,需要用到imagenet pretrain,由于网络是自己设计的网络,所以需要先在darknet上训练imagenet,由于网上都没有相关的说明教程,特别是图片路径 ...
- 全新Wijmo5中文学习指南正式上线
Wijmo 是一款使用 TypeScript 编写的新一代 JavaScript/HTML5 控件集.它秉承触控优先的设计理念,在全球率先支持 AngularJS,并且支持React.VueJS以及T ...