有趣的c语言

写在前面

工作三年多,常听各位前辈讲:

语言是其次的,重要的是思想。

深以为然,于是继续贯彻陶渊明的:

好读书,不求甚解。

结果,工作了三年多,稀里糊涂的代码写了不少,犯的错误也很多,吃一堑长一智的过程中,也会经常有很多不解:“这样写,难道不对么?”

最近终于有时间,终于还是决定花点时间,把c语言掌握的清晰一点,选中了这本已经带领无数大神走向巅峰的神书《c专家编程》,打算通读一遍,空口无凭,记个笔记,所以,到这里您可以返回了,这,只不过是一个简陋的读书笔记。。。

01 #define中的空格

记得最开始学习编程的时候,有听过一些说法,宏定义中的空格实际上没有作用,例如:

//01-1.1
#include     
                                                                                                                                                          
#define sum(a, b)       ((a) + (b))                                                                                                                                             
#define sum_1(a, b)     ((a)+(b))                                                                                                                                                                                                                                                                                                                 
#define sum_2(a,b) ((a)+(b))                                                                                                                                                    
                                                                                                                                                                                
int main(int argc, char* argv[])                                                                                                                                                
{                                                                                                                                                                               
    printf("sum = %d\n", sum(1, 1));                                                                                                                                            
    printf("sum_1 = %d\n", sum_1(1, 1));                                                                                                                                        
    printf("sum_2 = %d\n", sum_2(1, 1));                                                                                                                                        
    return 0;                                                                                                                                                                   
}          

预编译后的产物,其实是这样的:

//01-1.2
//...不相关,省略

int main(int argc, char* argv[])
{
 printf("sum = %d\n", ((1) + (1)));
 printf("sum_1 = %d\n", ((1)+(1)));
 printf("sum_2 = %d\n", ((1)+(1)));
 return 0;
}

上面的空格的确不会影响结果输出,而且,行尾多加的几个空格也没有什么用处,然而另外一些情况,显然不是如此:

//01-2.1.1
#define a(y) a_expanded(y)

int a_expanded(int y)
{
        return y + 10;
}

int main(int argc, char* argv[])
{
        int x = 100;

        a(x);
        return 0;
}

预编译后如下:

//01-2.1.2
//...不相关,省略

int main(int argc, char* argv[])
{
 int x = 100;

 a_expanded(x);
 return 0;
}

而下面这段代码,加了两个空格之后,含义却完全不同:

//01-2.2.1
#define a (y) a_expanded (y)

int a_expanded(int y)
{
        return y + 10;
}

int main(int argc, char* argv[])
{
        int x = 100;

        a(x);
        return 0;
}

预编译后的产物却是这样:

//01-2.2.2
//...不相关,省略

int main(int argc, char* argv[])
{
 int x = 100;

 (y) a_expanded (y)(x);
 return 0;
}

那么显然,空格影响了宏定义的含义,当然,代码01-2.2.1实际上无法执行,因此,宏定义中,显然还是要小心的注意某些位置空格的问题。

02 const修饰的是谁?

int foo(const char** p)                                                                                                                                                         
{                                                                                                                                                                                                                                                                                                                                             
}                                                                                                                                                                               
                                                                                                                                                                                
int main(int argc, char** argv)                                                                                                                                                 
{                                                                                                                                                                               
    foo(argv);                                                                                                                                                                  
}                 

上面这段例子里,编译会报warning:

const.c: In function ‘main’:
const.c:8:9: warning: passing argument 1 of ‘foo’ from incompatible pointer type [-Wincompatible-pointer-types]
     foo(argv);
         ^~~~
const.c:1:5: note: expected ‘const char **’ but argument is of type ‘char **’
 int foo(const char** p)
     ^~~

那么显然,编译器认为,char**并不能直接复制给const char **,会发生隐式类型转换,可是,下面的例子却很常见:

int foo(const char* p)                                                                                                                                                          
{         
}                                                                                                                                                                                
int main(int argc, char* argv)                                                                                                                                                  
{                                                                                                                                                                               
    foo(argv);                                                                                                                                                                  
}                                                                                                                                                                               

那么,一个简单的问题就是,c语言的参数传递实际上是一个赋值过程,那么char*可以赋值给const char*,为什么char **不能赋值给const char**

两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须包含右边指针所指向类型的全部限定符。

那么,上面主要有两处需要注意:

  1. 赋值的左右两边必须类型相容。
  2. 左边必须包含右边的全部限定符。

根据这两个条件,解释以下这个问题:
char*是一个指向没有限定符char类型的指针。
const char*是一个有const限定符char类型的指针。
char类型相容,左边包含右边的限定符,因此可以赋值。

char**是一个指向char类型的指针的指针。
const char**是一个指向有const限定符限制的char类型的指针的指针。
那么显然,二者均没有限定符,且是指针,前者指向char*,后者指向const char*,二者不相容,也因此,char**实际上与const char**不相容,因此,上面的编译会报warning。

看完上面的例子,并不能让人很容易明白,很多情况下依然是一头雾水,那么,再举个例子,看看const到底修饰的是谁?

#include                                                                                                                                                               
                                                                                                                                                                                
void test_const_1(void)                                                                                                                                                         
{                                                                                                                                                                               
    char arr[][10] = {"abc", "def", "hij"};                                                                                                                                     
                                                                                                                                                                                
    const char *pStr = arr[0];                                                                                                                                                  
    const char *pStr1 = arr[1];                                                                                                                                                 
    const char *pStr2 = arr[2];                                                                                                                                                 
                                                                                                                                                                                
    const char **ppStr = &pStr;                                                                                                                                                 
    const char **ppStr1 = &pStr1;                                                                                                                                               
                                                                                                                                                                                
    const char ***pppStr = &ppStr;                                                                                                                                              
                                                                                                                                                                                
    pStr = pStr1;                                                                                                                                                               
    *pStr = arr[1][1];                                                                                                                                                          
                                                                                                                                                                                
    ppStr = &pStr1;                                                                                                                                                             
    *ppStr = pStr1;                                                                                                                                                             
    **ppStr = arr[1][1];                                                                                                                                                        
                                                                                                                                                                                
    pppStr = &ppStr1;                                                                                                                                                           
    *pppStr = &pStr1;                                                                                                                                                           
    **pppStr = pStr1;                                                                                                                                                           
    ***pppStr = arr[1][1];                                                                                                                                                      
}                                                                                                                                                                               
                                                                                                                                                                                
void test_const_2(void)                                                                                                                                                         
{                                                                                                                                                                               
    char arr[][10] = {"abc", "def", "hij"};                                                                                                                                     
                                                                                                                                                                                
    const char *pStr = arr[0];                                                                                                                                                  
    const char *pStr1 = arr[1];                                                                                                                                                 
    const char *pStr2 = arr[2];                                                                                                                                                 
                                                                                                                                                                                
    const char **ppStr = &pStr;                                                                                                                                                 
    const char **ppStr1 = &pStr1;                                                                                                                                               
                                                                                                                                                                                
    const char ***pppStr1 = NULL;                                                                                                                                               
    char* const **pppStr2 = NULL;                                                                                                                                               
    char** const *pppStr3 = NULL;                                                                                                                                               
                                                                                                                                                                                
    pppStr1 = &ppStr;                                                                                                                                                           
    *pppStr1 = &pStr1;                                                                                                                                                          
    **pppStr1 = pStr1;                                                                                                                                                          
    ***pppStr1 = arr[1][1];                                                                                                                                                     
    
    /*!
     * 错误用法,赋值表达式左边不包含右边的全部限定                                                                                                                             
     * ppStr中const修饰char,表示不能通过**ppStr修改char的值                                                                                                                    
     * pppStr2中const修饰char *,表示不能通过**pppStr2修改char* 的指向
     */
    //pppStr2 = &ppStr;
    /*!
     * 错误用法,赋值表达式左边不包含右边的全部限定
     * pStr1中const修饰char,表示不能通过*pStr1修改char的值
     * pppStr2中const修饰char *,表示不能通过**pppStr2修改char* 的指向
     */
    //*pppStr2 = &pStr1;//! 隐式类型转换
    **pppStr2 = pStr1;
    ***pppStr2 = arr[1][1];
    
    /*!
     * 错误用法,赋值表达式左边不包含右边的全部限定
     * ppStr中const修饰char,表示不能通过**ppStr修改char的值
     * pppStr3中const修饰char**,表示不能通过*pppStr3修改char** 的指向
     */
    //pppStr3 = &ppStr;
    *pppStr3 = &pStr1;
    /*!
     * 错误用法,赋值表达式左边不包含右边的全部限定
     * pStr1中const修饰char,表示不能通过*pStr1修改char的值
     * pppStr3中const修饰char**,表示不能通过*pppStr3修改char** 的指向
     */
    //**pppStr3 = pStr1;
    ***pppStr3 = arr[1][1];
}   

int main(int argc, char* argv[])
{
    test_const_1();

    test_const_2();

    return 0;
}

猜猜看,上面哪些内容会编译报错?

const.c: In function ‘test_const_1’:
const.c:17:8: error: assignment of read-only location ‘*pStr’
  *pStr = arr[1][1];
        ^
const.c:21:10: error: assignment of read-only location ‘**ppStr’
  **ppStr = arr[1][1];
          ^
const.c:26:12: error: assignment of read-only location ‘***pppStr’
  ***pppStr = arr[1][1];
            ^
const.c: In function ‘test_const_2’:
const.c:47:13: error: assignment of read-only location ‘***pppStr1’
  ***pppStr1 = arr[1][1];
             ^
const.c:61:12: error: assignment of read-only location ‘**pppStr2’
  **pppStr2 = pStr1;
            ^
const.c:70:11: error: assignment of read-only location ‘*pppStr3’
  *pppStr3 = &pStr1;
           ^

经过上面的例子,可以简单的记忆为,const修饰的指针变量中,去掉数据类型,const后面的变量不可直接修改,例如char** const *pppStr3*pppStr3不可以直接赋值修改。

你可能感兴趣的:(语言学习)