工作三年多,常听各位前辈讲:
语言是其次的,重要的是思想。
深以为然,于是继续贯彻陶渊明的:
好读书,不求甚解。
结果,工作了三年多,稀里糊涂的代码写了不少,犯的错误也很多,吃一堑长一智的过程中,也会经常有很多不解:“这样写,难道不对么?”
最近终于有时间,终于还是决定花点时间,把c语言掌握的清晰一点,选中了这本已经带领无数大神走向巅峰的神书《c专家编程》,打算通读一遍,空口无凭,记个笔记,所以,到这里您可以返回了,这,只不过是一个简陋的读书笔记。。。
记得最开始学习编程的时候,有听过一些说法,宏定义中的空格实际上没有作用,例如:
//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实际上无法执行,因此,宏定义中,显然还是要小心的注意某些位置空格的问题。
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**
?
两个操作数都是指向有限定符或无限定符的相容类型的指针,左边指针所指向的类型必须包含右边指针所指向类型的全部限定符。
那么,上面主要有两处需要注意:
根据这两个条件,解释以下这个问题:
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
不可以直接赋值修改。