zoukankan      html  css  js  c++  java
  • 理解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[0] = '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', '' };
    char s2[] = { 'd', 'e', 'f', '' };
    char* ss1[] = { &s1[0], &s2[0] };
    char* ss2[] = { &s1[0], &s2[0] };
    const char** p = (const char**)ss1;  // 需要强制转换
    p = (const char**)ss2; // 编译通过
    p[0] = &s2[0];         // 编译通过
    p[0][0] = '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[0] = &s1[0];     // 编译报错
    p[0][0] = '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[0] = &s1[0];     // 编译通过
    p[0][0] = '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、如果它是糖,你把它当成钻石,你就不会去咬,牙齿还是完好的,至少避免了不小心咬到钻石的危险。

  • 相关阅读:
    【liunx】使用xshell连接虚拟机上的CentOS 7,使用xhell连接本地虚拟机上的Ubuntu, 获取本地虚拟机中CentOS 7的IP地址,获取本地虚拟机中Ubuntu 的IP地址,linux查看和开启22端口
    eclipse启动tomcat, http://localhost:8080无法访问
    java合并PDF,itext.jar
    关于给springboot添加定时器的两种方式
    HttpClient post提交数据,汉字转码
    java 中文与unicode互转
    微信公众号里的图片下载并显示
    根据url下载图片和页面
    java获取指定日期之前或之后的时间
    java计算文件大小
  • 原文地址:https://www.cnblogs.com/Harley-Quinn/p/7190110.html
Copyright © 2011-2022 走看看