理解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),从而 ...
随机推荐
- 35-THREE.JS 多面体
<!DOCTYPE html> <html> <head> <title></title> <script src="htt ...
- C语言实现的矩阵乘法运算器
需求 编写程序模拟矩阵乘法,一个m*n的矩阵,乘以一个 n*m 的矩阵, 并且输出结果. STEP 1 矩阵乘法的计算 一个m行n列的矩阵与一个n行p列的矩阵可以相乘,得到的结果是一个m行p列的矩阵 ...
- DRF中两大视图基类APIView/GenericAPIView的介绍
(1)APIView rest_framework.views.APIView APIView是REST framework提供的所有视图的基类,继承自Django的View父类. APIView与V ...
- 剑指offer--45.二叉树的深度
时间限制:1秒 空间限制:32768K 热度指数:139716 题目描述 输入一棵二叉树,求该树的深度.从根结点到叶结点依次经过的结点(含根.叶结点)形成树的一条路径,最长路径的长度为树的深度. -- ...
- jQuery选项卡wdScrollTab
实例Demo 运行一下 参数说明 Config active Number Active tab index. Base on 0. autoResizable Boolean Whether ...
- linux安装配置apache服务(httpd)
1. 安装 httpd. [root@linuxprobe ~]# yum -y install httpd 2. 删除默认欢迎页面 [root@linuxprobe ~]# rm -f /etc/h ...
- Vmware无法正常打开
VMware的问题 VMware总是搞一些事情,比如隔三差五就打不开了.那么怎么办呢?以前总要把Vm的安装程序重新开始一遍,然后选择修复 但是呢?这样太麻烦了,前几月做web开发的时候接触了Apach ...
- 实战maven私有仓库三部曲之三:Docker下搭建maven私有仓库
本章是<实战maven私有仓库>系列的第三篇,在前两章中,我们先在linux搭建maven私有仓库,然后在开发环境使用此仓库,本章我们在docker下快速搭建maven私有仓库,然后像前面 ...
- socket创建UDP服务端和客户端
UDP服务端代码示例: from socket import * #1.创建数据报套接字 sockfd = socket(AF_INET, SOCK_DGRAM) #2.绑定服务端地 sockfd.b ...
- HDU - 5876 :Sparse Graph (完全图的补图的最短路 -BFS&set)
In graph theory, the complement of a graph G is a graph H on the same vertices such that two distinc ...