首页
社区
课程
招聘
[原创]C语言学习抄书笔记
发表于: 2013-4-10 12:23 42909

[原创]C语言学习抄书笔记

2013-4-10 12:23
42909
收藏
免费 6
支持
分享
最新回复 (81)
雪    币: 2099
活跃值: (3479)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
26
楼主要是能整理出WORD格式的就更好了,呵,不过还是非常期待一步一步来.
2013-4-13 22:41
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
27
2.1变量名

    对变量的命名与符号常量的命名是有一定限制的。名字是由字母和数字组成的序列,其第一个字符必须为字母,并区分大小写。下划线“_”被视为字母,通常用于命名较长的变量名。

2.2 数据类型及长度

    C语言只提供下列几种基本数据类型:

char
int       有short和long两个限定符,使用时可省略int。
float
double    long double表示高精度的浮点数。

    含义都懂,不赘述。

    此外还有signed和unsigned两个可用于任何类型的限定符。

2.3 常量

int型          十进制:1234
               八进制:01234
               十六进制:0x1234
long型         十进制:1234l或1234L
               八进制:01234l或01234L
               十六进制:0x1234l、0x1234L或0X1234l、0X1234L
float型        123.4f、1e-2f或123.4F、1e-2F
double型       123.4、1e-2
long double型  123.4l、1e-2l或123.4L、1e-2L

unsigned同样适用与常量,通常使用后缀u或U来表示。

字符常量    'x'、'0'
转义字符    \n、\t
            \ooo 八进制数
            \xhh 十六进制数
字符串常量  "Hello, World!"
            从技术角度看,字符串常量就是字符数组。字符串使用一个空字符'\0'作为结尾。
枚举常量    enum boolean { no, yes }
            在没有显式说明的情况下,enum类型中第一个枚举名的值为0,第二个为1,依次类推。如果只指定了部分枚举名的值,那么未指定的枚举名的值将依照最后一个指定值向后递增。

2.4 声明

    所有变量必须先声明后使用也可在声明的同时进行初始化。任何变量的声明都可以使用const限定符,该限定符指定变量的值不能被修改。对数组而言,const限定数组所有元素的值都不能被修改。

2.5 算数运算符

    二元算数运算符:+、-、×、/、%。

    %不可用于float和double型。

2.6 关系运算符与逻辑运算符

关系运算符:    >、>=、<、<= 优先级比算数运算符低。
相等性运算符:  ==、!= 优先级仅次于关系运算符。
逻辑运算符:    &&与||。
                由&&与||连接的表达式按从左到右的顺序进行求值,并在知道结果值为真或为假后立即停止运算。
                优先级低于关系运算符和相等性运算符。
逻辑非运算符:  !。

2.7 类型转换

    当一个运算符的几个操作数类型不同时,就需要通过一些规则把它们转换为某种共同的类型。一般来说,自动转换是指把”比较窄的“操作数转换为”比较宽的“操作数且不丢失信息的转换。例如计算float+int时,就会把int自动转换为float。

    下面以函数atoi为例,来展示类型的自动转换。

int atoi(char s[])
{
    int i, n = 0;

    for(i = 0; s[i] >= '0' && s[i] <= '9'; i++)
        n = n * 10 + (s[i] - '0');

    return n;
}

int main()
{
    int i, n;
    char c;
    char s[100];

    for(i = 0; (c = getchar()) != '\n' && i < 100; i++)
        s[i] = c;
    
    n = atoi(s);
    printf("result : %d\n", n);
    
    return 0;
}


    在算数表达式n = n * 10 + (s[i] – '0')中,就将char型是s[i]自动转换为int型用于计算。

    同样,在赋值时也需要类型转换。当把较短的类型转换为较长的类型时,不存在问题。而把较长的类型转换为较短的类型时,超出的高位部分将被丢弃。

    比如:

int i;
char c;

i = c;
c = i;


    执行后,不存在问题。但若将两个语句颠倒次序,则执行后可能会丢失信息。

    强制转换:(类型名)表达式

    比如:

int n;
sqrt((double)n);


2.8 自增预算符与自减运算符

    n++和n--。

    这里面需要强调的是当运算符在变量前和在变量后其效果是不一样的。

    比如:

    x = n++;

    执行后,x=5, n=6。而

    x = ++n;

    执行后,x=6, n=6。

2.9 按位运算符

    C语言提供了6个位操作运算符。这些运算符只能作用于整型操作数,即char,short,int,long。

&    按位与
|    按位或
^    按位异或
<<   左移
>>   右移
~    按位求反

2.10 赋值运算符和表达式

i+=2 等价于 I = I + 2。其他二元算数运算符也适用。

2.11 条件表达式

if(a > b)
    z = a;
else
    z = b;


    等价于

z = (a >b) ? a : b;


    这里面需要注意的是:
    1、条件表达式实际上依然是一种表达式。如果a和b的类型不同,结果按照之前的转换规则决定。
    2、条件表达式中第一个表达式两边的括号可以省略,因为条件运算符?:的优先级非常低。

2.12 运算符优先级与求值次序

运算符                                    结合性
() [] -> .                               从左至右
! ~ ++ -- + - * (type) sizeof            从右至左
* / %                                    从左至右
+ -                                      从左至右
<< >>                                    从左至右
< <= > >=                                从左至右
== !=                                    从左至右
&                                        从左至右
^                                        从左至右
|                                        从左至右
&&                                       从左至右
||                                       从左至右
?:                                       从左至右
= += -= *= /= %= &= ^= |= <<= >>=        从右至左
,                                        从右至左

    最后,对第二章进行一下总结,本章主要讲了一些基础概念的东西。很多细节需要记住,但也不至于去死记硬背,多动动手,多写写程序,时间久了,自然也就记住了。

    别的没有了,话说回来,第二章真的是抄书啊。。。

    最后再吐槽一句,抄书倒没什么,排版真是累死人啊!!
2013-4-13 22:50
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
28
pdf格式的倒是有草稿。
2013-4-13 22:53
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
29
并非完全不会C语言,只是重新学习一遍。
2013-4-13 22:54
0
雪    币: 258
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
30
没有习题吗??
2013-4-14 18:02
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
31
习题没怎么看,那个答案网上有吧
2013-4-14 18:47
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
32
3.1 语句与控制块

    表达式后加分号(;),就变成了语句。如:

x = 0;
i++;
printf(...);


    用一对花括号”{”与”}”把一组声明和语句括在一起就构成了一个复合语句(也叫程序块),复合语句在语法上等价于单条语句。

3.2 if-else语句
3.3 else-if语句

if(...)
	…
else if(...)
	…
else if(...)
	…
else
	…


3.4 switch语句

switch()
{
	case:	…
	case: …
	default:...
}


3.5 while循环和for循环

while(...)
	…

for(表达式1; 表达式2; 表达式3)
	…


    其中for循环中可以加入逗号(,)间隔。如:

int i, j;

for(i=0, j=100; i<j; i++, j—)
	...


3.6 do-while循环

do
	…
while(...);


3.7 break语句和continue语句

3.8 goto语句与标号

…
…
…
goto flag;
…
…
…

flag:...


    本章值得记录的东西比较少,简单总结一下吧。

    第一点,if、while、for等语句的花括号“{”、“}”省略的问题。根据规范,单条语句可省,复合语句必须用花括号括起来。不过,网上也有很多优良的编程习惯建议大家无论如何都拿花括号括起来,以避免不必要的错误,且提高代码的可读性。

    第二点是关于goto语句的。先看看原书怎么写。
       

C语言提供了可随意滥用的goto语句以及标记跳转位置的标号。从理论上讲,goto语句是没有必要的,实践中不使用goto语句也可以很容易地写出代码。至此,本书中还没有使用goto语句。


    关键词:滥用,没有必要的,本书还没有使用。说实话,goto语句的风评一向不好。各类教程对goto语句的看法非常的一致,那就是很方便,但应尽量避免。

    我的观点是写程序就要养成一个良好的格式习惯。而这一良好的习惯不光要自己看着舒服,也要别人能看懂。记得前阵子在微博上看到一句话,说应该这样夸C语言程序员,哇!你写的程序真棒,不用注释都能看懂!

    众所周知,C语言有很多减短程序的技巧,但写代码的时候还是要把握好取舍,毕竟谁都不希望多年后自己都看不懂自己写的代码。就这么多,本章完毕。
2013-4-15 20:05
0
雪    币: 371
活跃值: (72)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
33
goto 个人感觉唯一好用的地方是

if(xxx)
goto errorandreturn:
//代码

errorandreturn:
//释放资源等等
2013-4-15 20:31
0
雪    币: 258
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
34
看完了aaaa
2013-4-15 20:39
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
35
嗯嗯,还有从超长循环嵌套的最里面直接跳出来。
while
    while
        while
            while
                while
                    goto flag

flag:...
2013-4-16 08:26
0
雪    币: 297
活跃值: (120)
能力值: ( LV5,RANK:60 )
在线值:
发帖
回帖
粉丝
36
有这种嵌套的我不认为是好代码,完全可以避免的,尤其是中间的代码一多了,根本不知道下面的那个
"}"对应的是上面的那个"{"。
2013-4-16 08:45
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
37
确实,这样的代码不是好代码。但不代表这样的代码不会出现,毕竟在某些情况下,这样的代码反而方便,比如排序啊,算法啊什么的。

就像goto一样,尽量避免,但不是完全不能用。存在即合理嘛。
2013-4-16 08:49
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
38
4.1 函数的基本知识
4.2 返回非整型值的函数

    原书这两节码了很多字,琢磨了半天不知道怎么概括。函数不就是函数嘛,只好拿例子简单说明一下。

int HelloWorld(char *str)
{
    printf("%s\n", str);
    return 1;
}

int main()
{
    char *str = "Hello, World!";

    HelloWorld(str);

	return 0;
}


    其中int HelloWorld(char *str)的就是函数。
    int为函数类型,()内的为函数参数。函数类型和函数参数都是可以根据需要来改变。

4.3 外部变量
4.4 作用域规则

    本节是在第一章的基础上更详细的叙述外部变量和作用域规则的优缺点及适用范围。不赘述了,还是使用时灵活掌握吧。

4.5 头文件

    简单地说就是.h文件。用于定义和声明。

4.6 静态变量

    用static声明限定外部变量与函数,可以将其后声明的对象的作用域限定为被编译源文件的剩余部分。如:

    static int flag = 0;

    静态变量简单地说就是仅供其所在的源文件中的函数使用,其他函数不能访问。

4.7 寄存器变量

    register声明告诉编译器,它所声明的变量在程序中使用频率较高。其思想是,将register变量放在机器的寄存器中,这样可以使程序更小、执行速度更快。但遗憾的是,编译器可以忽略此选项。

    register声明的形式如下:
       
	register int x;
	register char c;

       
    register声明只适用于自动变量以及函数的形式参数。

    实际使用时,底层硬件环境的实际情况对寄存器变量的使用会有一些限制。每个函数中只有很少的变量可以保存在寄存器中,且只允许某些类型的变量。过量的register声明会被编译器自动忽略。

4.8 程序块结构

    简单的来说就是函数花括号“{”和“}”中的内容。

4.9 初始化

    初始化主要用于变量和数组等。方法是在变量或数组名后紧跟一个等号和一个表达式。

4.10 递归

    C语言中的函数可以递归调用,即函数可以直接或间接调用自身。

    另外,递归并不节省存储器的开销,因为递归调用过程中必须在某个地方维护一个存储处理值的栈。

4.11 C预处理器

    从概念上讲,预处理器是编译过程中单独执行的第一个步骤。

4.11.1文件包含
    文件包含指令(即#include指令)使得处理大量的#define指令以及声明更加方便。在源文件中,任何形如:

    #include “文件名”

    或

    #include <文件名>

    的行都将被替换为由文件名指定的文件的内容。如果文件名用引号引起来,则在源文件所在位置查找该文件;如果在该位置没有找到文件,或者如果文件名是用尖括号<与>括起来的,则将根据相应的规则查找该文件。
               
4.11.2 宏替换

    宏定义形式如下:#define 名字 替换文本

    这是一种最简单的宏替换——后续所有出现名字记号的地方都将被替换为替换文本。

    替换文本可以是任意的,例如:

    #define forever for(;;)

    宏定义也是可以带参数的,例如:

    #define max(A, B) ((A) > (B) ? (A) : (B))

    当然,也可以通过#undef指令取消名字的宏定义,

4.11.3 条件包含

    还可以使用条件语句对预处理器本身进行控制,这种条件语句的值是在预处理执行的过程中进行计算。形式如下:

	#if …
		#define …
	#endif


    好了,笔记做完了。总结一下吧。说实话,这一章写的我十分痛苦,也十分不满意。一来这两天工作杂事比较多,有些繁琐,时间和精力不太够。然而本章讲的主要是一些基础理论性的东西,十分繁杂。不好全文照抄,却又不能简单的概括出来,颇有一种意会而不可言传的感觉。东西都明白,道理也觉得很浅显,但就是概括不出来。转念一想,其实这也能理解,如果轻易的概括出来,想必原作者也就不用费那么多笔墨了。

    我想今后如果可能的话,我会重头完善本章。至少做到自己满意。

    本章结束。下一章,指针与数组。连续两章偏理论的内容了,终于来了个有料的章节。我对下一章很期待,特别是指针。
2013-4-18 21:17
0
雪    币: 446
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
39
非常感谢,学熊。
2013-4-19 23:34
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
40
5.1 指针与地址

指针是一种保存变量地址的变量。
一元运算符&可用于取一个对象的地址。如,

p = &c;


将把c的地址赋值给变量p,我们称p为“指向”c的指针。

一元运算符*是间接寻址或间接引用运算符。当它作用于指针时,将访问指针所指向的对象。

int x = 1, y = 2, z[10];
int *ip;	        //ip是一个int型指针
ip = &x;	        //将x的地址赋给ip
y = *ip;	        //将ip所指向的值赋给y
*ip = 0;	        //将ip所指向的值改为0
ip = &z[0];	//将z[0]的地址赋给ip


5.2 指针与函数参数

我们都知道C语言是以传值的方式将参数值传递给被调用函数,而被调用函数不能直接修改主调函数中变量的值。但当参数为指针时情况就不一样了,这里直接用代码展示一下指针用作函数参数的威力。

void swap(int x, int y)
{
	int tmp;
	
	tmp = x;
	x = y;
	y = temp;
}


swap函数看似实现了一个交换x,y值的功能,但当你使用swap(a, b)时,发现a和b的值并没有被交换。但如果参数不再是x和y,而是x和y的地址会怎么样呢?

void swap(int *px, int *py)
{
	int tmp;
	
	tmp = *px;
	*px = *py;
	*py = tmp;
}


这时通过swap(&a, &b)就可以达到交换a和b的值的目的。这是因为当a和b的地址传入swap后,其地址指向的值发生了变化,而地址本身没有改变。指针参数使得被调用函数能够访问和修改主调函数中对象的值。

5.3 指针与数组

原书毫不客气地讲,


通过数组下标能完成的任何操作都可以通过指针来实现。一般来说,用指针编写的程序比用数组下标编写的程序执行速度快,但另一方面,用指针实现的程序理解起来稍微困难一些。


原作者真是为了拔高指针的威力而不惜非常强硬的打压数组的作用。不过,偏偏我对这些东西格外敏感。虽说计算机已经强大到可以忽略这些微弱的执行效率差距。

举例表示下指针和数组的关系。

int a[10];
int *pa;
pa = &a[0]; //也可以写为 pa = &a;

*(pa + i) == a[i]; 


在计算数组元素a[i]的值时,C语言实际上先将其转换为*(a + i)的形式,然后再进行求值。直接使用指针来操作时即可省去转换这一步,这就是执行速度会有所提升的原因。

5.4 地址算术运算

这一节以一个不完善的存储分配程序为例,来展示地址算术运算。它有两个函数组成,第一个函数alloc(n)返回一个指向n个连续字符存储单元的指针,alloc函数的调用者可利用该指针存储字符序列;第二个函数afree(p)释放已分配的存储空间,以便以后重用。之所以说这两个函数是“不完善的”,是因为对afree函数的调用次序必须与调用alloc函数的次序相反。即alloc与afree以栈的方式进行存储空间的管理。

#define ALLOCSIZE 10000

static char allocbuf[ALLOCSIZE];
static char *allocp = allocbuf;	

char *alloc(int n)
{
    if(allocbuf + ALLOCSIZE - allocp >= n)
    {
        allocp += n;
        return allocp - n;
    }
    else
        return 0;
}

void afree(char *p)
{
    if(p >= allocbuf && p < allocbuf + ALLOCSIZE)
        allocp = p;
}


其中数组allocbuf是用来alloc的空间,allocp则是指向下一个空闲单元的指针。

5.5 字符指针与函数

char *pmsg = “this is a string”;


其实这一节和5.2的意思差不多,只不过是字符型而已。拿例子简单说吧。

void strcopy(char *s, char *t) 
{ 
    while((*s++ = *t++)) 
        ; 
} 

int main() 
{ 
    char *str = "I am a good man!"; 
    char *tmp = malloc(strlen(str)); 

    strcopy(tmp, str); 
    printf("%s\n", tmp); 
    free(tmp);

    return 0; 
}


这里需注意的是字符串最后一位'\0'也相当于是0,因此可拿最后一位当做条件判断。

5.6 指针数组以及指向指针的指针

指针本身也是变量,因此指针也可以像其他变量一样存储在数组中。下面以UNIX程序sort的一个简化版本为例介绍这一点。该程序按字母顺序对由文本行组成的集合进行排序。

排序过程包括下列三个步骤:

读取所有输入行
对文本行进行排序
按次序打印文本行

#include <stdio.h> 
#include <string.h> 
#include <stdlib.h> 

char *lineptr[5000]; 

void swap(char *v[], int i, int j) 
{ 
    char *temp; 

    temp = v[i]; 
    v[i] = v[j]; 
    v[j] = temp; 
} 

void MiniSort(char *v[], int left, int right) 
{ 
    int i, last; 

    if(left >= right) 
        return; 

    swap(lineptr, left, (left + right)/2); 
    last = left; 

    for(i = left + 1; i <= right; i++) 
        if(strcmp(lineptr[i], lineptr[left]) < 0) 
            swap(lineptr, ++last, i); 

    swap(lineptr, left, last); 
    MiniSort(lineptr, left, last - 1); 
    MiniSort(lineptr, last + 1, right); 
} 

void WriteLine(char *lineptr[], int maxline) 
{ 
    while(maxline-- > 0) 
        printf("%s\n", *lineptr++); 

} 

int GetLine(char line[], int maxline) 
{ 
	int c, i; 
	 
	for(i = 0; i < maxline -1 && (c = getchar()) != EOF && c != '\n'; i++) 
		line[i] = c; 

	line[i] = '\0'; 

	return i; 
} 

int ReadLine(char *lineptr[], int maxline) 
{ 
	int len, nline; 
	char *p, line[1000]; 

	nline = 0; 
	while((len = GetLine(line, 1000)) > 0) 
	{ 
		if(nline >= maxline || (p = malloc(len)) == NULL) 
			return -1; 
		else 
		{ 
			line[len] = '\0'; 
			strcpy(p, line); 
			lineptr[nline++] = p; 
		} 
	}

	return nline; 
} 

int main() 
{ 
    int nline; 

    if((nline = ReadLine(lineptr, 1000)) > 0) 
    { 
        MiniSort(lineptr, 0, nline - 1); 
        WriteLine(lineptr, nline); 
    } 
    else 
    { 
        printf("Error: input too big to sort\n"); 
    } 
    return 0; 
}


5.7 多维数组

C语言提供了类似于矩阵的多维数组。

int array[i][j][k]...;


5.8 指针数组的初始化

char *str[] = {
	“this is string1”,
	“this is string2”, “this is string3”, “this is string4”,
	“this is string5”, “this is string6”,
	“this is string7”, “this is string8”, …
	…
	…
}


5.9 指针与多维数组

举例来说:
int a[10][20];
int *b[10];


从语法角度来讲,a[3][4]和b[3][4]都是对一个int对象的合法引用。但a是一个真正的二维数组,他分配了200个int型长度的存储空间。但对b来说,他仅仅分配了十个指针,并且没有对他们初始化。

5.10 命令行参数

在支持C语言的环境中,可以在程序开始执行时将命令行参数传递给程序。在调用主函数main()时, 他带有两个参数。第一个参数(习惯上成argc,用于参数计数)的值表示运行程序时命令行中参数的数目;第二个参数(称为argv,用于参数向量)是一个指向字符串数组的指针,其中每一个字符串对应一个参数。

#include <stdio.h> 

int main(int argc, char *argv[]) 
{ 
	while(--argc > 0) 
		printf((argc > 1) ? "%s " : "%s", *++argv); 

	printf("\n"); 

	return 0; 
}

       
其中,
        (argc > 1) ? "%s " : "%s"是判断当前参数后是否要加空格。

5.11 指向函数的指针
5.12 复杂声明

在C语言中,函数本身不是变量,但可以定义指向函数的指针。

int *f();		//函数f()返回一个int 型指针
int (*pf)();	//指针pf指向一个int型函数


这两节个人感觉过于繁琐,且意义不大。留待以后学习笔记中再说了。(个人愚见,盼指教。)

这一章笔记做的周期比较长。主要是因为最近手头上的事比较繁琐,工作的,生活的,弄得人焦头烂额的。后面的笔记则尽可能勤快些。好了,本章结束。
2013-4-28 21:49
0
雪    币: 47147
活跃值: (20485)
能力值: (RANK:350 )
在线值:
发帖
回帖
粉丝
41
设精华鼓励一下,抄书也不容易。
2013-4-30 22:40
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
42
感动啊~~~
2013-5-1 14:34
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
43
    结构是一个或多个变量的集合,这些变量可能为不同的类型,为了处理的方便而将这些变量组织在一个名字之下。

6.1 结构的基本知识

    关键字struct引入结构声明。如:

struct point {
        int x;
        int y;
};


    struct后的名字是可选的,称为结构标记。结构中定义的变量称为成员。

6.2 结构与函数

    结构的合法操作只用几种:作为一个整体复制和赋值,通过&运算符取地址,访问其成员。

    如果传递给函数的结构很大,使用指针方式的效率通常比复制整个结构的效率要高。

struct point *pp;


    pp指向一个point结构,*(pp).x和*(pp).y是结构成员。为了使用方便,C语言提供了另一种简写方式,pp->x和pp->y。

6.3 结构数组

    这一节直接举例子来说。编写一个程序统计输入中各个C语言关键字出现的次数。

struct key {
        char *word;
        int count;
} keytab[100];


    也可表示成

struct key {
        char *word;
        int count;
};

struct key keytab[100];


    附上代码。

#include <stdio.h>
#include <ctype.h>
#include <string.h>

struct key {
        char *word;
        int count;
} keytab[] = {
        auto", 0,
        "break", 0,
        "case", 0,
        "char", 0,
        "const", 0,
        "continue", 0,
        "default", 0,
        /* ... */
        "unsigned", 0,
        "void", 0,
        "volatile", 0,
        "while", 0
};

#define NKEYS (sizeof keytab / sizeof(struct key))

int BinSearch(char *word, struct key tab[], int n)
{
        int cond;
        int low, high, mid;

        low = 0;
        high = n - 1;
        while(low <= high)
        {
                mid = (low + high)/2;
                if((cond = strcmp(word, tab[mid].word)) < 0)
                        high = mid - 1;
                else if(cond > 0)
                        low = mid + 1;
                else
                        return mid;
        }

        return -1;
}

int GetWord(char *word, int lim)
{
        int c;
        char *w = word;

        while((c = getchar()) != ' ')
                *w++ = c;

        *w = '\0';
        return word[0];
}

int main()
{
        int n;
        char word[100];

        while(GetWord(word, 100) != '\0')
        {
                if(isalpha(word[0]))
                {
                        if((n = BinSearch(word, keytab, NKEYS)) >= 0)
                        keytab[n].count++;
                }
        }

        for(n = 0; n < NKEYS; n++)
        {
                if(keytab[n].count > 0)
                        printf("%4d %s\n", keytab[n].count, keytab[n].word);
        }

        while(1)
                ;
        return 0;
}


    这里需要说明几点:1、GetWord函数是我自己写的。不知道为什么照着书上的例子抄下来会陷入死循环。看着他实现的怪麻烦的,干脆重新写个算了。2、输入单词需后加空格才会被统计。3、最后连输两个空格作为输入完成标记。

    其实本节的重点在于keytab[]的声明,至于这个很挫的GetWord纯粹是为了完成任务罢了。功能简陋些,勉强将就一下。

6.4 指向结构的指针

    本节修改前例main和BinSearch函数,来说明指向结构的指针。

struct key *BinSearch(char *word, struct key *tab, int n)
{
        int cond;
        struct key *low = &tab[0];
        struct key *high = &tab[n];
        struct key *mid;

        while(low < high)
        {
                mid = low + (high - low)/2;
                if((cond = strcmp(word, mid->word)) < 0)
                        high = mid;
                else if(cond > 0)
                        low = mid + 1;
                else
                        return mid;
        }

        return NULL;
}

int main()
{
        char word[100];
        struct key *p;

        while(GetWord(word, 100) != '\0')
        {
                if(isalpha(word[0]))
                {
                        if((p = BinSearch(word, keytab, NKEYS)) != NULL)
                                p->count++;
                }
        }

        for(p = keytab; p < keytab + NKEYS; p++)
        {
                if(p->count > 0)
                        printf("%4d %s\n", p->count, p->word);
        }

        while(1)
                ;

        return 0;
}


    这里需要说明一下,两个指针之间的加法运算是非法的。但指针减法运算确实合法的,因此mid = low + (high - low)/2不能写成mid = (low + high)/2。

6.5 自引用结构

    这一节会拿一个更为一般化的问题来说明自引用结构。

    该例子的功能是统计输入中所有单词的出现次数。因为预先设置好单词列表是不现实的,因此要在读取输入中单词的同时,就将他放到正确的位置,从而始终保持所有单词是按顺序排列的。这里用二叉树来存放单词列表。

#include <stdio.h>
#include <ctype.h>
#include <string.h>
#include <stdlib.h>

struct tnode
{ 
        char *word;
        int count;

        struct tnode *left;
        struct tnode *right;
};

int GetWord(char *word, int lim)
{
        int c;
        char *w = word;

        while((c = getchar()) != ' ')
                *w++ = c;

        *w = '\0';
        return word[0];
}

struct tnode *talloc()
{
        return (struct tnode *)malloc(sizeof(struct tnode));
}

struct tnode *AddTree(struct tnode *p, char *word)
{
        int cond;

        if(p == NULL)
        {
                p = talloc();
                p->word = _strdup(word);
                p->count = 1;
                p->left = p->right = NULL;
        }
        else if((cond = strcmp(word, p->word)) == 0)
                p->count++;
        else if(cond < 0)
                p->left = AddTree(p->left, word);
        else
                p->right = AddTree(p->right, word);

        return p;
}

void TreePrint(struct tnode *p)
{
        if(p != NULL)
        {
                TreePrint(p->left);
                printf("%4d %s\n", p->count, p->word);
                TreePrint(p->right);
        }
}

int main()
{
        struct tnode *tree;
        char word[100];
        
        tree = NULL;
        while(GetWord(word, 100) != '\0')
        {
                if(isalpha(word[0]))
                tree = AddTree(tree, word);
        }

        TreePrint(tree);

        while(1)
                ;

        return 0;
}


    程序本身很好理解,唯一需要说明的是在结构tnode中,struct tnode *left中,left是指向tnode的指针,并非tnode本身。

6.6 表查找

    这一节貌似只是通过一个链表查找来加深对结构的了解。因此,略。

6.7 类型定义(typedef)

    C语言提供了一个称为typedef的功能,用来建立新的数据类型名。如,

typedef int Length;


    将typedef定义为与int具有同等意义的名字。

6.8 联合

    联合是可以(在不同时刻)保存不同类型和长度的对象的变量,编译器负责跟踪对象的长度和对其要求。

union u_tag {
        int ival;
        float fval;
        char *sval;
} u;


    变量u必须足够大,以保存这3种类型中最大的一种。

6.9 位字段

struct {
        unsighed int is_keyword : 1;
        unsighed int is_extern : 1;
        unsighed int is_static : 1;
} flags;


    这里定义了一个变量flags,他包含3个一位的字段。冒号后的数字表示字段的宽度(二进制位数表示)。

    总结一下,本章的主题很鲜明,就是结构。并通过举例说明了结构的各种形式。感觉这章写的很畅快,但却又总结不出什么话来。总之,结构很方便,也很重要。在编程中应尽量用,大胆的用。用多了,自然就熟了,也就不会出错了。

    另外扯点闲话,我又回到了windows平台。gcc是个好编译器,我喜欢。vs2010也是个好编辑器,我也喜欢。只不过我还需要windows下的lightroom做一些别的事情,wine下的photoshop用着太别扭了。这或许会影响到后续课程的选择。不过有些遥远,先不想。

    本章完。

2013-5-3 23:46
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
44
7.1 标准输入 / 输出

    最简单的输入 / 输出机制----getchar()和putchar()。

#include <stdio.h>

int main()
{
	int c;

	while((c = getchar()) != EOF)
		putchar(c);

	return 0;
}


7.2 格式化输出——printf函数

int printf(char *format, arg1, arg2, ...);


7.3 变长参数表

    本节以实现函数printf的一个最简单版本为例,介绍如何以可移植的方式编写可处理变长参数表的函数。

#include <stdarg.h>
#include <stdio.h>

void MiniPrintf(char *fmt, ...)
{
	va_list ap;
	char *p, *sval;
	int ival;
	double dval;

	va_start(ap, fmt);
	for(p = fmt; *p; p++)
	{
		if(*p != '%')
		{
			putchar(*p);
			continue;
		}

		switch(*++p)
		{
		case 'd':
			ival = va_arg(ap, int);
			printf("%d", ival);
			break;
		case 'f':
			dval = va_arg(ap, double);
			printf("%f", dval);
			break;
		case 's':
			for(sval = va_arg(ap, char *); *sval; sval++)
				putchar(*sval);
			break;
		default:
			putchar(*p);
			break;
		}
	}
	va_end(ap);
}

int main()
{
	MiniPrintf("int = %d, float = %f, char * = %s", 1, 1.234, "I am MiniPrintf!");

	getchar();
	return 0;
}


    这里需要说明的有:1、这个例子真的毫无意义啊。。。我本以为是通过putchar来输出呢,结果MiniPrintf里调用printf。2、va_list类型用于声明一个变量,该变量将依次引用各参数。每次调用va_arg时,该函数都将返回一个参数,并将ap指向下一个参数。va_end完成必要的清理工作。

7.4 格式化输入——scanf函数

int scanf(char *format, ...);


7.5 文件访问
7.6 错误处理——stderr和exit

直接举例说明。

#include <stdio.h>

int main(int argc, char *argv[])
{
	FILE *fp1, *fp2;
	int c;

	fp1 = fp2 = NULL;

	if(argc == 1)
	{
		while((c = fgetc(stdin)) != EOF)
			fputc(c, stdout);
		return 0;
	}

	if(argc != 3)
		printf("Error Arg! (%s Org.txt Det.txt)\n", argv[0]);
	else
	{
		if((fp1 = fopen(argv[1], "rb")) == NULL)
			fprintf(stderr, "%s cannot Open %s!\n", argv[0], argv[1]);
		else
		{
			if((fp2 = fopen(argv[2], "wb")) == NULL)
				printf("Error Create File!\n");
			else
			{
				while((c = fgetc(fp1)) != EOF)
					fputc(c, fp2);
			}
		}

		if(ferror(stdout))
			fprintf(stderr, "%s: error writing stdout\n", argv[0]);
	}

	if(fp1 != NULL)
		fclose(fp1);
	if(fp2 != NULL)
		fclose(fp2);

	return 0;
}


7.7 行输入和行输出

行输入:
char *fgets(char *line, int maxline, FILE *fp);

行输出:
int fputs(char *line ,FILE *fp);


7.8 其他函数

    7.8.1 字符串操作函数
    7.8.2 字符类别测试和转换函数
    7.8.3 ungetc函数
    7.8.4 命令执行函数
    7.8.5 存储管理函数
    7.8.6 数学函数
    7.8.7 随机数发生器函数

    总结一下,本章主要介绍了输入输出函数以及基本的文件操作。因为本章内容太过实践化,着实没有什么可抄的。且后续的指针学习还会涉及到这里,所以本章的笔记较为简略。这一章最后介绍的一些功能函数,这里就不一一罗列了。需要的时候自行msdn吧。
另外,第八章介绍UNIX系统接口。因为我已回归windows平台,所以直接过了。再后面是附录,也过了吧。有需要的时候再翻了。
本书结束。开启下一本书《C指针 编程之道》。
指针乃C语言的精髓,所以就重点学习一下。当然也会注意进度,争取早日进入《算法导论》的学习周期。指针依然是C语言的内容,所以就不开新帖了。等到《算法导论》的时候再开吧。
2013-5-4 22:01
0
雪    币: 18
活跃值: (33)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
45
学完后,发现自己还是个码农。这是事实
2013-5-4 22:41
0
雪    币: 941
活跃值: (1264)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
46
抄,也是一种学习,值得肯定,不过有两点,我想说说:
第一:抄名著,抄经典的,如果英文可以,那样就一举多得;
其二:坚持,反复
2013-5-4 22:58
0
雪    币: 440
活跃值: (1178)
能力值: ( LV3,RANK:30 )
在线值:
发帖
回帖
粉丝
47
我也跟着楼主学c
2013-5-4 23:00
0
雪    币: 200
活跃值: (10)
能力值: ( LV2,RANK:10 )
在线值:
发帖
回帖
粉丝
48
很久没来了,lz加油!
2013-5-5 09:39
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
49
离开看雪你就不是码农了。真的。
2013-5-5 10:24
0
雪    币: 250
活跃值: (25)
能力值: ( LV4,RANK:50 )
在线值:
发帖
回帖
粉丝
50
关键是英语没那么可以。看懂是没问题,就是太费劲了。
2013-5-5 10:25
0
游客
登录 | 注册 方可回帖
返回
//