##########################
一, 类型,运算符与表达式
##########################
所有整型都包括sign(带符号)和unsign(无符号)两种形式.
C语言只提供了下面几种基本数据类型:
char, int, float, double
一个字符常量是一个整数,书写时将一个字符括在单引号中,如: 'x'.
字符串常量也叫字符串字面值, 是用双引号括起来的0个或多个字符组成的字符序列.
我们已经字符常量与仅包含一个字符的字符串之间的区别: 'x'与"x"是不同的.前者是一个整数,其值是字母x在机器字符集中对应的数值;
后者是一个包含一个字符以及结束符'\0'的字符数组.
枚举常量是另外一种类型的常量,是一个常量整型值的列表:
enum boolean {
NO, YES
};
所有变量都必须先声明后使用.
任何变量的声明都可以使用const限定符限定,表明变量的值不能被修改.
取模运算符 % 不能应用于float或double类型.
C语言中,很多情况下会进行隐式的算术类型转.一般来说如果二元运算符的两个操作数具有不同的类型,那么在进行运算之前先把较低的类型提升为较高的类型.
强制类型转换: (类型名) 表达式
-------------
##########################
二, 函数与程序结构
main() {...}
那么在push()函数中,不需声明就可以访问变量sp, 但是sp却不能在main()中使用,
push()也不能在main()中使用.
如果要在外部变量的定义之前使用该变量, 必须在相应的变量声明中使用关键字: extern.
用static声明限定的外部变量与函数,可以将其的作用域限定为被编译源文件的剩余部分.通过static限定外部对象,可以达到隐藏外部对象的目的.
要将对象指定为静态存储,可以在正常的对象声明之前加上关键字static作为前缀.
通常情况下,函数名字是全局可以访问的,
但是如果吧函数声明为static,则该函数除了对该函数声明所在的文件可见外,其他文件都无法访问.
外部变量和静态变量都属于静态区存储,不同的是静态变量限定了作用域(同源文件).
register声明的变量放在机器的寄存器中,这样可以更快.
register声明只适用于自动变量以及函数的形式参数.
无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的.
C语言中,不允许在函数中定义函数.
数组的初始化可以在声明的后面紧跟一个初始化表达式列表:
int days[] = {31, 28, 31, 30, ... }
字符数组的初始化比较特殊:
char pattern[] = "some"; //它同下面的声明是等价的:
char pattern[] = {'s','o','m','e','\0'}
宏替换中的替换文本是任意的:
#define forever for ( ; ; )
宏定义也可以带参数:
#define max(A, B) ((A) > (B) ? (A) : (B))
使用宏max看起来就像是使用函数:
x = max(p + q, r + s);
可以通过#undef指令取消名字的宏定义:
#undef getchar
形式参数不能用带引号的字符串替换,但是,如果在替换文本中,参数名以#作为前缀,
则结果将被扩展为由实际参数替换该参数的带引号的字符串:(这翻译真垃圾)
#define dprint(expr) printf(#expr " = %g\n", expr)
使用语句: dprint(x/y);
调用宏: printf("x/y" " = %g\n", x/y);
等价于: printf("x/y = %g\n", x/y);
预处理器运算符 ## 为宏扩展提供了一种连接实际参数的手段:
#define paste(front, back) front ## back
宏调用paste(name, 1)的结果将建立记号name1.
预处理的条件控制可以使用 #if ... #elif ... #else ... #endif:
#if !defined(HDR)
#define HDR
...
#endif
C语言专门定义了两个预处理语句: #ifdef 与 #ifndef, 用来测试某个名字是否已经定义.
-----------------
##########################
三, 指针与数组
##########################
指针是一种保存变量地址的变量.
地址运算符&只能应用于内存中的对象,即变量与数组元素.
不能作用于表达式,常量或register变量.
一元运算符*是间接寻址或间接引用运算符.当它作用于指针时,将访问指针所指向的对象.
double *dp, atof(char *);这个表达式表明*dp
和atof(s)的值都是double类型,且atof的参数是一个指向char类型的指针.
*ip += 1; 等同于 ++*ip 或 (*ip)++
指针与数组:
int a[10];
int *pa;
pa = &a[0];
"指针加1"就意味着, pa+1 指向下一个对象,
下标和指针运算之间具有密切的对应关系.根据定义,数组类型的变量或表达式的值是该数组第0个元素的地址.
那么pa = &a[0]也可以写成下面形式:
pa = a;
对数组元素a[i]的引用也可以改成*(a+i)形式.
&a[i]和a+i的含义是相同的,a+i是a之后第i个元素的地址.
但是我们必须记住,数祖名和指针之间有一个不同之处: 指针是变量,数祖名不是变量.pa
=a和pa++是合法的, 但a = pa 和 a++ 是非法的.
进栈和出栈的标准用法:
*p++ = val; // 将val压入栈
val = *--p; // 将栈顶元素弹出到val中
/* strcpy函数: 将指针t指向的字符串复制到指针指向的位置 */
版本1(数组下标实现):
void strcpy(char *s, char *t) {
while (s[i] = t[i] != '\0')
i++;
}
版本2(指针方式实现):
void strcpy(char *s, char *t) {
while ((*s = *t) != '\0') {
s++;
t++;
}
}
版本3(指针):
void strcpy(char *s, char *t) {
while ((*s++ = *t++) != '\0')
;
}
版本4(指针):
void strcpy(char *s, char *t) {
while (*s++ = *t++)
;
}
char *lineptr[MAXLINES];
这个表达式表示lineptr是一个具有MAXLINES个元素的一维数组,其中数组的每个元素是一个指向字符类型对象的指针.
看下面这两个定义:
int a[10][20];
int *b[10];
从语法角度讲,a[3][4]和b[3][4]都是对一个int对象的合法引用.但a是一个真正的二维数组,它分配了200个int类型的存储空间.
而对b来说,仅仅是分配了10个指针,并且没有对他们初始化.
指针数组的一个重要的优点在于,数组的每一行长度可以不同.
C语言中,主函数main()可以带两个参数:argc(参数数量), argv(指针,对应每个函数).
看下面的例子: 可以实现echo,比如echo hello world会打印hello world;
main(int argc, char *argv) {
int i;
for (i = 1; i < argc; i++) {
printf("%s%s", argv[i], (i < argc - 1) ? " " : "");
}
printf("\n");
return 0;
}
其中核心代码可以优化:
while (--argc > 0) {
printf((argc > 1) ? "%s " : "%s", *++argv);
}
UNIX系统中的C语言有个约定:以负号开头的参数表示一个可选标志或参数,如 -x.
一般在程序里这样获取: ... (*++argv)[0] == '-'
在C语言中,函数本身不是变量,但可以定义指向函数的指针.
这种类型的指针可以被赋值,存放在数组中,传递给函数以及作为函数的返回值.
指向函数的指针使函数"变量化"了.
int (*comp)(void *, void *);
它表明comp是一个指向函数的指针,该函数具有两个void*类型的参数.其返回值为int.
int *comp(void *, void *);
则表明comp是一个函数,该函数返回一个指向int类型的指针.
下面是一个复杂的声明:
char (*(*x())[])();
表明x是一个函数, 它返回一个指针,
该指针指向一个一维数组,该数组的元素为指针,这些指针分别指向多个函数,这些函数的返回值为char类型.
------------
##########################
四, 结构
##########################
结构是一个或多个变量的结合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下.
struct point {
int x;
int y;
}
struct后面的名字是可选的,结构中定义的变量称为成员.
struct声明定义了一种数据类型:
struct {...} x, y, z;
从语法角度来说,这种声明方式与 int x, y, z;具有类似的意义.
struct point pt; 定义了一个struct point类型的变量pt;
在表达式中,可以通过下列形式引用某个结构中的成员:
结构名.成员
结构可以嵌套:
struct rect {
struct point pt1;
struct point pt2;
}
下面函数带有两个整型参数, 并返回一个point类型的结构:
struct point makepoint(int x, int y) {
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}
如果传递给函数的结构很大,使用指针方式的效率比较高,结构指针类似于普通变量指针:
struct point *pp;
如果pp指向一个point结构, 那么*pp即为该结构,(*pp).x则是结构成员:
struct point origin, *pp;
pp = &origin;
printf("origin is (%d,%d)\n", (*pp).x, (*pp).yy);
其中的圆括号是必须的, "."优先级比"*"高.
为了方便,C语言提供了另一种结构指针的写法. 假设p是一个指向结构的指针,可以用:
p->结构成员
所有运算符中,下面4个运算符优先级最高:
结构运算符"."和"->",用于函数调用的"()"以及用于下标的"[]".
结构声明和对变量的类型定义可以写在一起:
struct key {
char *word;
int count;
} keytab[NKEYS];
C语言提供了一个编译时一元运算符sizeof, 它可用来计算任一对象的长度,
但条件编译语句#if中不能使用sizeof.
一个包含其自身实例的结构是非法的, 但是, 下列声明是合法的:
struct tnode {
char *word;
int count;
struct tnode *left;
struct tnode *right;
}
它将left声明为指向tnode的指针,而不是tnode实例本身.
C语言提供了一个称为typedef的功能,它用来建立新的数据类型名:
typedef int Length;
将Length定义为与int相同意义的类型名字.它没有创建新的类型,只是一个增加了新名称而已.
struct {
unsigned int is_keyword : 1;
unsigned int is_extern : 1;
unsigned int is_static : 1;
} flags;
这里定义了一个变量flags, 它包含3个一位的字段.
冒号后的数字表示字段的宽度(用二进制位数表示).
一, 类型,运算符与表达式
##########################
所有整型都包括sign(带符号)和unsign(无符号)两种形式.
C语言只提供了下面几种基本数据类型:
char, int, float, double
一个字符常量是一个整数,书写时将一个字符括在单引号中,如: 'x'.
字符串常量也叫字符串字面值, 是用双引号括起来的0个或多个字符组成的字符序列.
我们已经字符常量与仅包含一个字符的字符串之间的区别: 'x'与"x"是不同的.前者是一个整数,其值是字母x在机器字符集中对应的数值;
后者是一个包含一个字符以及结束符'\0'的字符数组.
枚举常量是另外一种类型的常量,是一个常量整型值的列表:
enum boolean {
NO, YES
};
所有变量都必须先声明后使用.
任何变量的声明都可以使用const限定符限定,表明变量的值不能被修改.
取模运算符 % 不能应用于float或double类型.
C语言中,很多情况下会进行隐式的算术类型转.一般来说如果二元运算符的两个操作数具有不同的类型,那么在进行运算之前先把较低的类型提升为较高的类型.
强制类型转换: (类型名) 表达式
-------------
##########################
二, 函数与程序结构
##########################
main() {...}
int sp = 0;
那么在push()函数中,不需声明就可以访问变量sp, 但是sp却不能在main()中使用,
push()也不能在main()中使用.
如果要在外部变量的定义之前使用该变量, 必须在相应的变量声明中使用关键字: extern.
用static声明限定的外部变量与函数,可以将其的作用域限定为被编译源文件的剩余部分.通过static限定外部对象,可以达到隐藏外部对象的目的.
要将对象指定为静态存储,可以在正常的对象声明之前加上关键字static作为前缀.
通常情况下,函数名字是全局可以访问的,
但是如果吧函数声明为static,则该函数除了对该函数声明所在的文件可见外,其他文件都无法访问.
外部变量和静态变量都属于静态区存储,不同的是静态变量限定了作用域(同源文件).
register声明的变量放在机器的寄存器中,这样可以更快.
register声明只适用于自动变量以及函数的形式参数.
无论寄存器变量实际上是不是存放在寄存器中,它的地址都是不能访问的.
C语言中,不允许在函数中定义函数.
数组的初始化可以在声明的后面紧跟一个初始化表达式列表:
int days[] = {31, 28, 31, 30, ... }
字符数组的初始化比较特殊:
char pattern[] = "some"; //它同下面的声明是等价的:
char pattern[] = {'s','o','m','e','\0'}
宏替换中的替换文本是任意的:
#define forever for ( ; ; )
宏定义也可以带参数:
#define max(A, B) ((A) > (B) ? (A) : (B))
使用宏max看起来就像是使用函数:
x = max(p + q, r + s);
可以通过#undef指令取消名字的宏定义:
#undef getchar
形式参数不能用带引号的字符串替换,但是,如果在替换文本中,参数名以#作为前缀,
则结果将被扩展为由实际参数替换该参数的带引号的字符串:(这翻译真垃圾)
#define dprint(expr) printf(#expr " = %g\n", expr)
使用语句: dprint(x/y);
调用宏: printf("x/y" " = %g\n", x/y);
等价于: printf("x/y = %g\n", x/y);
预处理器运算符 ## 为宏扩展提供了一种连接实际参数的手段:
#define paste(front, back) front ## back
宏调用paste(name, 1)的结果将建立记号name1.
预处理的条件控制可以使用 #if ... #elif ... #else ... #endif:
#if !defined(HDR)
#define HDR
...
#endif
C语言专门定义了两个预处理语句: #ifdef 与 #ifndef, 用来测试某个名字是否已经定义.
-----------------
##########################
三, 指针与数组
##########################
指针是一种保存变量地址的变量.
地址运算符&只能应用于内存中的对象,即变量与数组元素.
不能作用于表达式,常量或register变量.
一元运算符*是间接寻址或间接引用运算符.当它作用于指针时,将访问指针所指向的对象.
double *dp, atof(char *);这个表达式表明*dp
和atof(s)的值都是double类型,且atof的参数是一个指向char类型的指针.
*ip += 1; 等同于 ++*ip 或 (*ip)++
指针与数组:
int a[10];
int *pa;
pa = &a[0];
"指针加1"就意味着, pa+1 指向下一个对象,
下标和指针运算之间具有密切的对应关系.根据定义,数组类型的变量或表达式的值是该数组第0个元素的地址.
那么pa = &a[0]也可以写成下面形式:
pa = a;
对数组元素a[i]的引用也可以改成*(a+i)形式.
&a[i]和a+i的含义是相同的,a+i是a之后第i个元素的地址.
但是我们必须记住,数祖名和指针之间有一个不同之处: 指针是变量,数祖名不是变量.pa
=a和pa++是合法的, 但a = pa 和 a++ 是非法的.
进栈和出栈的标准用法:
*p++ = val; // 将val压入栈
val = *--p; // 将栈顶元素弹出到val中
/* strcpy函数: 将指针t指向的字符串复制到指针指向的位置 */
版本1(数组下标实现):
void strcpy(char *s, char *t) {
int i;
i = 0;while (s[i] = t[i] != '\0')
i++;
}
版本2(指针方式实现):
void strcpy(char *s, char *t) {
while ((*s = *t) != '\0') {
s++;
t++;
}
}
版本3(指针):
void strcpy(char *s, char *t) {
while ((*s++ = *t++) != '\0')
;
}
版本4(指针):
void strcpy(char *s, char *t) {
while (*s++ = *t++)
;
}
char *lineptr[MAXLINES];
这个表达式表示lineptr是一个具有MAXLINES个元素的一维数组,其中数组的每个元素是一个指向字符类型对象的指针.
看下面这两个定义:
int a[10][20];
int *b[10];
从语法角度讲,a[3][4]和b[3][4]都是对一个int对象的合法引用.但a是一个真正的二维数组,它分配了200个int类型的存储空间.
而对b来说,仅仅是分配了10个指针,并且没有对他们初始化.
指针数组的一个重要的优点在于,数组的每一行长度可以不同.
C语言中,主函数main()可以带两个参数:argc(参数数量), argv(指针,对应每个函数).
看下面的例子: 可以实现echo,比如echo hello world会打印hello world;
main(int argc, char *argv) {
int i;
for (i = 1; i < argc; i++) {
printf("%s%s", argv[i], (i < argc - 1) ? " " : "");
}
printf("\n");
return 0;
}
其中核心代码可以优化:
while (--argc > 0) {
printf((argc > 1) ? "%s " : "%s", *++argv);
}
UNIX系统中的C语言有个约定:以负号开头的参数表示一个可选标志或参数,如 -x.
一般在程序里这样获取: ... (*++argv)[0] == '-'
在C语言中,函数本身不是变量,但可以定义指向函数的指针.
这种类型的指针可以被赋值,存放在数组中,传递给函数以及作为函数的返回值.
指向函数的指针使函数"变量化"了.
int (*comp)(void *, void *);
它表明comp是一个指向函数的指针,该函数具有两个void*类型的参数.其返回值为int.
int *comp(void *, void *);
则表明comp是一个函数,该函数返回一个指向int类型的指针.
下面是一个复杂的声明:
char (*(*x())[])();
表明x是一个函数, 它返回一个指针,
该指针指向一个一维数组,该数组的元素为指针,这些指针分别指向多个函数,这些函数的返回值为char类型.
------------
##########################
四, 结构
##########################
结构是一个或多个变量的结合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下.
struct point {
int x;
int y;
}
struct后面的名字是可选的,结构中定义的变量称为成员.
struct声明定义了一种数据类型:
struct {...} x, y, z;
从语法角度来说,这种声明方式与 int x, y, z;具有类似的意义.
struct point pt; 定义了一个struct point类型的变量pt;
在表达式中,可以通过下列形式引用某个结构中的成员:
结构名.成员
结构可以嵌套:
struct rect {
struct point pt1;
struct point pt2;
}
下面函数带有两个整型参数, 并返回一个point类型的结构:
struct point makepoint(int x, int y) {
struct point temp;
temp.x = x;
temp.y = y;
return temp;
}
如果传递给函数的结构很大,使用指针方式的效率比较高,结构指针类似于普通变量指针:
struct point *pp;
如果pp指向一个point结构, 那么*pp即为该结构,(*pp).x则是结构成员:
struct point origin, *pp;
pp = &origin;
printf("origin is (%d,%d)\n", (*pp).x, (*pp).yy);
其中的圆括号是必须的, "."优先级比"*"高.
为了方便,C语言提供了另一种结构指针的写法. 假设p是一个指向结构的指针,可以用:
p->结构成员
所有运算符中,下面4个运算符优先级最高:
结构运算符"."和"->",用于函数调用的"()"以及用于下标的"[]".
结构声明和对变量的类型定义可以写在一起:
struct key {
char *word;
int count;
} keytab[NKEYS];
C语言提供了一个编译时一元运算符sizeof, 它可用来计算任一对象的长度,
但条件编译语句#if中不能使用sizeof.
一个包含其自身实例的结构是非法的, 但是, 下列声明是合法的:
struct tnode {
char *word;
int count;
struct tnode *left;
struct tnode *right;
}
它将left声明为指向tnode的指针,而不是tnode实例本身.
C语言提供了一个称为typedef的功能,它用来建立新的数据类型名:
typedef int Length;
将Length定义为与int相同意义的类型名字.它没有创建新的类型,只是一个增加了新名称而已.
struct {
unsigned int is_keyword : 1;
unsigned int is_extern : 1;
unsigned int is_static : 1;
} flags;
这里定义了一个变量flags, 它包含3个一位的字段.
冒号后的数字表示字段的宽度(用二进制位数表示).