C陷阱与缺陷 读书笔记

如果一个整形常量的第一个字符是数字0,那么该常量将被视作八进制数。
例如,10与010的含义截然不同;
#include <stdio.h>
 
int main(int argc, char *argv[])
{
    char hello[] = {'H','i','v','i','e','w','\n','\0'};
    char *str = "lzhsdly";
 
    printf(hello);
    printf(str);
    printf("\n");
    
    return 0;
}
用单引号括起来的一个字符代表一个整数,如'a'的含义与0141(八进制)或者97(十进制)严格一致;
用双引号括起来的一个字符代表一个指针;
例如:char *slash = '/';
在编译时将会产生一条错误信息,因为'/'并不是一个字符指针;
又如:printf('\n');来代替正确的printf("\n");
会在程序运行的时候产生难以预料的错误,而不会给出编译器诊断信息(因为某些C编译器对函数参数并不进行类型检查,特别是对printf函数的参数)
float *g();
因为()结合优先级高于*,*g()也就是*(g()),<==>(float *)g();
表示g是一个函数,该函数的返回值类型为指向浮点数的指针;
float (*h)();
表示h是一个函数指针,h所指向函数的返回值为浮点类型;
或表示h是一个指向返回值为浮点类型的函数的指针;
 
一旦知道了如何声明一个给定类型的变量,那么该类型的类型转换符就很容易得到了:只需要把声明中的变量名和声明末尾的分号去掉,再将剩余的部分用一个括号整个"封装"起来即可。
例如,因为下面的声明:float (*h) ();
表示h是一个指向返回值为浮点类型的函数的指针,因此,
(float (*) ())
表示一个"指向返回值为浮点类型的函数的指针"的类型转换符;
 
当计算机启动时,硬件将调用首地址为0位置的子例程,我们设计一个C语句,以显示调用子例程:
分析:(*(void(*)())0) ();
1.假定变量fp是一个函数指针,那么如何调用fp所指向的函数呢?调用方法如下:
    (*fp) ();
因为fp是一个函数指针,那么*fp就是该指针所指向的函数,所以(*fp)()就是调用该函数的方式;
2.找到一个恰当的表达式来替换fp.(对0作类型转换)
我们一旦知道如何声明一个变量,也就自然知道如何对一个常数进行类型转换,将其转型为该变量的类型:只需要在变量声明中将变量名去掉即可。
因此,将常数0转型为"指向返回值为void的函数的指针"类型,可以这样写:
(void (*) ())0
因此,我们可以利用(void (*) ())0来替换fp,从而得到:
(*(void (*) ())0) ();
 
①void int_handler();  ---普通的函数声明
 
②void (*int_handler)();  ---函数指针的声明 ,即声明了一个函数指针,此函数是没有返回值和形参的函数,即①中的函数。  
 
③typedef void (*int_handler)(); ---用typedef定义一种函数指针的类型,也就是说这里的int_handler 就代表了②中的函数指针类型。
   可以这么用:
         typedef void (*int_handler)();
         int_handler   phandler;      -----此处的phandler就和②中的int_handler是一样的。
 
C语言允许初始化列表中出现多余的逗号,如:
int days[] = {1,2,3,4,5,6,};
int calendar[12][31];
sizeof(calendar)的值是31*12与sizeof(int)的乘积;
 
int a[] = {1,2,3};
sizeof(a)的结果是整个数组a的大小,而不是指向数组a的元素的指针的大小;
除了a被用作运算符sizeof的参数这一情形,在其它所有的情形中数组名a都代表指向数组a中下标为0的元素的指针。
*(a+i)即数组a中下标为i的元素的引用,被简记为a[i];
 
P46 C陷阱与缺陷
声明指向数组的指针的方法:
int calendar[12][31];
int *p;
 
p = calendar;
这个语句是非法的。
 
因为calendar是一个二维数组,即“数组的数组”,在此处的上下文中使用calendar名称会将其转换为一个指向数组的指针;
而p是一个指向整型变量的指针,这个语句试图将一种类型的指针赋值给另一种类型的指针,所以是非法的;
很显然,我们需要一种声明指向数组的指针的方法:
int (*monthp)[31];//声明了*monthp是一个拥有31个整形元素的数组,因此,monthp就是一个指向这样的数组的指针。
monthp = calendar;
这样,monthp将指向数组calendar的第一个元素,也就是数组calendar的12个有着31个元素的数组类型元素之一;
 
在C语言中,字符串常量代表了一块包括字符串中所有字符以及一个空字符('\0')的内存区域的地址;
strlen返回参数中字符串所包括的字符数目,而作为结束标志的空字符并未计算在内;
C语言中会自动地将作为参数的数组声明转换为相应的指针声明,
int strlen(char s[])
{
    /**/
}
与下面的写法完全相同,
int strlen(char* s)
{
    /**/
}
 
C语言中只有四个运算符(&&、||、?:和,)存在规定的求值顺序;
对于数组结尾之后的下一个元素,取它的地址是合法的;
C语言中存在两类整数算数运算,有符号运算和无符号运算。
在无符号算数运算中,没有所谓的"溢出"一说;如果算数运算符的一个操作数是有符号整数,另一个是无符号整数,那么有符号整数会被转换为无符号整数,"溢出"也不可能发生。但是,当两个操作数都是有符号整数时,"溢出"就有可能发生;
 
extern int a;
这个语句说明了a是一个外部整形变量,但是因为它包括了extern关键字,这就显示地说明了a的存储空间是在程序的其它地方分配的;
每个外部变量只能够定义一次;
static int a;
a的作用域限制在一个源文件内,对于其它源文件,a是不可见的;
如果若干个函数需要共享一组外部对象,可以将这些函数放到一个源文件中,把它们需要用到的对象也都在同一个源文件中以static修饰符声明;
static修饰符不仅适用于变量,也适用于函数。如果函数f需要调用另一个函数g,而且只有函数f需要调用函数g,我们可以把函数f与函数g都放到同一个源文件中,并且声明函数g为static:
static int g(int x)
{
    /*g函数体*/
}
 
void f()
{
    /*其它内容*/
    b = g(a);
}
我们可以在多个源文件中定义同名的函数g,只要所有的函数g都被定义为static,或者仅仅只有其中一个函数g不是static。因此,为了避免可能出现的命名冲突,如果一个函数仅仅被同一个源文件中的其它函数调用,我们就应该声明该函数为static。
 
一个函数如果形参列表为空,在被调用时实参列表也为空;
任何一个C函数都有返回类型,要么是void,要么是函数生成结果的类型;
 
如果任何一个函数在调用它的每个文件中,都在第一次被调用之前进行了声明或定义,那么就不会有任何与返回类型相关的麻烦。例如,函数square计算它的双精度类型参数的平方值:
double square(double x)
{
    return x*x;
}
以及一个调用square函数的程序:
main()
{
    printf("%g\n",square(0.3));
}
要使这个程序能够运行,函数square必须要么在main之前进行定义:
double square(double x)
{
    return x*x;
}
 
main()
{
    printf("%g\n",square(0.3));
}
要么在main之前进行声明:
double square(double);
 
main()
{
    printf("%g\n",square(0.3));
}
 
double square(double x)
{
    return x*x;
}
如果一个函数在被定义或声明之前被调用,那么它的返回类型就默认为整形。
上面的例子中,如果将main函数单独抽取出来作为一个源文件:
main()
{
    printf("%g\n",square(0.3));
}
因为函数main假定函数square返回类型为整形,而函数square返回类型实际上是双精度类型,当它与square函数连接时就会得出错误的结果。
如果我们需要在两个不同的文件中分别定义函数main与函数square,那么应该如何处理呢?函数square只能有一个定义。如果square的调用与定义分别位于不同的文件中,那么我们必须在调用它的文件中声明square函数:
double square(double);
 
main()
{
    printf("%g\n",square(0.3));
}
 
c语言中%g是什么意思?使用一般浮点数或者科学计数法中长度较短的格式来输出
当程序要求scanf读入一个整数,应该传递给它一个指向整数的指针,如:
int i;
scanf("%d",&i);
C语言中的规则是,如果一个未声明的标识符后跟一个开括号,那么它将被视为一个返回整形的函数;
在C语言中要用到write和read函数要用到什么头文件?
Well, strictly speaking, read() and write() are _not_ stantard library functions. So its usage platform dependent.
 
Anyway, on Linux platform,
#include <unistd.h>
should do the work.
 
EOF:一个在头文件stdio.h中被定义的值,不同于任何一个字符;
fseek函数要求第二个参数是long类型,因为int类型的整数可能无法包含一个文件的大小;
 
大多数C语言实现在函数调用时都会带来重大的系统开销。因此,我们希望有这样一种程序块,它看上去像一个函数,但却没有函数调用的开销。举例来说,getchar和putchar经常被实现为宏,以避免在每次执行输入或者输出一个字符这样简单的操作时,都要调用相应的函数而造成系统效率的下降。
 
空格在宏定义和宏调用中的区别:
宏定义
#define f (x) ((x)-1)
中的f代表(x) ((x)-1),而对于
#define f(x) ((x)-1),
其宏调用中f(3)与f (3)求值后都等于2;
 
断言assert是一个宏,其定义如下:
#define assert(e) \
    ((void) ((e) || _assert_error(_FILE_,_LINE_)))
这个定义实际上利用了||运算符对两侧的操作数依次顺序求值的性质。
如果e为true(真),表达式:
(void) ((e) || _assert_error(_FILE_,_LINE_))
的值在没有求出其右侧表达式
_assert_error(_FILE_,_LINE)
的值的情况下就可以确定最终的结果为真。如果e为false(假),右侧表达式
_assert_error(_FILE_,_LINE)
的值必须求出,此时_assert_error将被调用,并打印出一条恰当的"断言失败"的出错消息。
_FILE_和_LINE_是内建于C语言于处理器中的宏,它们会被扩展为所在文件的文件名和所处代码行的行号;
 
 
 
规则:每个外部对象只在一个地方声明。这个声明的地方一般就在一个头文件中,需要用到该外部对象的所有模块都应该包括这个头文件。特别需要指出的是,定义该外部对象的模块也应该包括这个头文件。
例如,我们希望能够做到只在一处改动这个特定的对象名,所有模块中的对象名就同时得到更新。我们可以这样来做,创建一个文件,比如叫做file.h,包含了声明:
extern char filename[];
需要用到外部对象filename的每个C文件都应该加上这样一个语句:
#include "file.h"
最后,我们选择一个C源文件,在其中给出filename的初始值。我们不妨称这个文件为file.c:
#include "file.h"
char filename[] = "/etc/passwd";
注意,源文件file.c实际上包含filename的两个声明,这一点只要把include语句展开就可以看出:
extern char filename[];
char filename[] = "/etc/passwd";
效果:头文件file.h中声明了filename的类型,因此每个包含了file.h的模块也就自动地正确声明了filename的类型。源文件file.c定义了filename,由于它也包含了file.h头文件,因此filename定义的类型自动地与声明的类型相符合。如果编译所有这些文件,filename的类型就肯定是正确的;
 
程序输出有两种方式:一种是即时处理,另一种是先暂存起来,然后再大块写入的方式,前者往往造成较高的系统负担。因此,C语言实现通常都允许程序员进行实际的写操作之前控制产生的输出数据量。
这种控制能力一般是通过库函数setbuf实现的。如果buf是一个大小适当的字符数组,那么,setbuf(stdout,buf);
语句将通知输入/输出库,所有写入到stdout的输出都应该使用buf作为输出缓冲区,直到buf缓冲区被填满或者程序员直接调用fflush,buf缓冲区的内容才实际写入到stdout中。
对于由写操作打开的文件,调用fflush将导致输出缓冲区的内容被实际地写入该文件;
缓冲区的大小由头文件<stdio.h>中的BUFSIZ定义;
 
宏并不是类型定义
宏的一个常见用途是,使多个不同变量的类型可以在一个地方说明:
#define FOOTYPE struct foo
FOOTYPE a;
FOOTYPE b,c;
这样,编程者只需在程序中改动一行代码,即可改变a、b、c的类型,而与a、b、c
在程序中的什么地方声明无关。
 
宏定义的这种用法有一个优点————可移植性,得到了所有C编译器的支持。但是,我们最好还是使用类型定义:
typedef strcut foo FOOTYPE;
这个语句定义了FOOTYPE为一个新的类型,与struct foo完全等效。
这两种命名类型的方法似乎都差不多,但是使用typedef的方式要更加通用一些。例如,考虑下面代码:
#define T1 struct foo *
typedef struct foo *T2;
从上面两个定义来看,T1和T2从概念上完全相同,都是指向结构foo的指针。
但是,当我们试图用它来声明多个变量时,问题就来了:
T1 a,b;
T2 a,b;
第一个声明被扩展为:
struct foo * a,b;
这个语句中a被定义为一个指向结构的指针,而b却被定义为一个结构(而不是指针)。第二个声明则不同,它定义了a和b都是指向结构的指针,因为这里T2的行为完全与一个真实的类型相同。
 
移位比除法的运行速度快的多;
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值