『C程序设计』读书笔记
关键字:c语言
原作者姓名:loose_went
文章原出处:vczx.com ---http://www.vczx.com/minute_c.php
写在前面:
《C程序设计》可以说是一本再基础不过的编程书了,但每读一遍的感觉却都是不同的,可以说,每读一遍,都会有很多新的收获。真所谓老书再读,回味无穷啊!此笔记是《C程序设计》谭浩强编著,清华大学出版社出版。除了将书中的重点知识点记下来外,也加入了我对知识点的理解,我想这一点是读书笔记的重要性所在。
第一章 概述 第二章 数据类型、运算符与表达式
第三章 最简单的c程序设计 第四章 逻辑运算和判断选取控制
第五章 循环控制 第六章 数组
第七章 函数 第八章 预编译处理
第九章 指针 第十章 结构体与共用体
第十一章 位运算 第十二章 文件
第一章 概述
1. C语言的特点
①语言简洁、紧凑,使用方便、灵活。共有32个关键字,9种控制语句。
②运算符丰富,公有34种运算符。
③数据结构丰富,数据类型有:整型、实型、字符型、数组、指针、结构体、共用体等。
④具有结构化的控制语句(如if…else、while、do…while、switch、for)
⑤语法限制不太严格,程序设计自由度大。
⑥允许直接访问物理地址,能进行位(bit)操作,可以直接对硬件操作。
⑦生成目标代码质量高,程序执行效率高。
⑧可移植性好。
2. C语言的用途
C虽不擅长科学计算和管理领域,但对操作系统和系统实用程序以及对硬件进行操作方面,C有明显的优势。现在很多大型应用软件也用C编写。
Top of Page
第二章 数据类型、运算符与表达式
1. C的数据类型
C的数据类型包括:整型、字符型、实型或浮点型(单精度和双精度)、枚举类型、数组类型、结构体类型、共用体类型、指针类型和空类型。
2. 常量与变量
常量其值不可改变,符号常量名通常用大写。变量其值可以改变,变量名只能由字母、数字和下划线组成,且第一个字符必须为字母或下划线。否则为不合法的变量名。变量在编译时为其分配相应存储单元。
3. 整型数据
整型常量的表示方法:十进制不用说了,八进制以0开头,如0123,十六进制以0x开头,如0x1e。
整型变量分为:基本型(int)、短整型(short int)、长整型(long int)和无符号型。不同机器上各类数据所占内存字节数不同,一般int型为2个字节,long型为4个字节。
4. 实型数据
实型常量表示形式:十进制形式由数字和小数点组成(必须有小数点),如:0.12、.123、123.、0.0等。指数形式如123e3代表123×10的三次方。
实型变量分为单精度(float)和双精度(double)两类。在一般系统中float型占4字节,7位有效数字,double型占8字节,15~16位有效数字。
5. 字符型数据
字符变量用单引号括起来,如'a','b'等。还有一些是特殊的字符常量,如'/n','/t'等。分别代表换行和横向跳格。
字符变量以char 来定义,一个变量只能存放一个字符常量。
字符串常量是由双引号括起来的字符序列。这里一定要注意'a'和"a"的不同,前者为字符常量,后者为字符串常量,c规定:每个字符串的结尾加一个结束标志'/0',实际上"a"包含两个字符:'a'和'/0'。
6. 数值型数据间的混合运算
整型、字符型、实型数据间可以混合运算,运算时不同类型数据要转换成同一类型再运算,转换规则:
char,short -> int -> unsigned -> long -> double <- float
7. 运算符和表达式
c运算符包括:
算数运算符( + - * / % )
关系运算符( > < == >= <= != )
逻辑运算符( ! && || )
位运算符( << >> ~ | ^ & )
赋值运算符( = )
条件运算符( ? : )
逗号运算符( , )
指针运算符( * & )
求字节数( sizeof )
强制类型转换(类型)
分量运算符( . -> )
下标运算符( [ ] )
其它运算符( 如函数调用运算符( ) )
自增自减运算符( ++ -- )注意:++i和i++的不同之处,++i使用i之前先使i加1,i++使用i之后,使i加1。
逗号表达式的求解过程:先求解表达式1,再求解表达式2,整个表达式的值是表达式2的值。
Top of Page
第三章 最简单的c程序设计
1.c的9种控制语句:
if() ~ else~
for()~
while()~
do~while()
continue
break
switch
goto
return
程序的三种基本结构:顺序结构,选择结构,循环结构
2.数据输出
c语言不提供输入输出语句,输入输出操作是由c的库函数完成。但要包含头文件stdio.h。
putchar( ) 向终端输出一个字符
printf( )的格式字符:
① d格式符 用来输出十进制整数
%d 按整型数据的实际长度输出
%md 使输出长度为m,如果数据长度小于m,则左补空格,如果大于m,则输出实际长度
%ld 输出长整型数据
② o格式符 以八进制形式输出整数
③ x格式符 以十六进制形式输出整数
④ u格式符 用来输出unsigned型数据,以十进制形式输出
⑤ c格式符 用来输出一个字符
⑥ s格式符 输出一个字符串
%s 输出实际长度字符串
%ms 输出的串占m列,如果串长度小于m,左补空格,如果大于m,实际输出
%-ms输出的串占m列,如果串长度小于m,右补空格,
%m.ns 输出占m列,但只取字符串中左端n个字符并靠右对齐
%-m.ns m、n含义同上,靠左对齐,如果n>m,则m自动取n值
⑦ f格式符 以小数形式输出实数
%f 整数部分全部输出,小数部分输出6位
%m.nf 输出数据共占m列,其中有n位小数。如果数值长度小于m,左补空格
%-m.nf 同上,右补空格
⑧ e格式符 以指数形式输出实数
%e 系统指定6位小数,5位指数(e+002 )
⑨ g格式符 输出实数,根据数值大小,自动选f格式或e格式
3.数据输入
getchar( ) 从终端输入一个字符
scanf( 格式控制,地址列表) 标准C scanf中不使用%u,对于unsigned型数据,以%d或%o或%x输入。%后的*,用来跳过它相应的数据。输入数据时不能规定精度如scanf( "%7.2f", &a );是不合法的。
Top of Page
第四章 逻辑运算和判断选取控制
1. 关系运算符:
c提供6种关系运算符(> < <= >= == != )前四种优先级高于后两种。
2. If语句
C提供了三种形式的if语句
If(表达式) 语句
If(表达式) 语句1 else 语句2
If(表达式1) 语句1
Else if(表达式2) 语句2
…
else 语句n
3. 条件运算符
(a>b)?a:b 条件为真,表达式取值a,否则取值b
4. Switch语句
Switch(表达式)
{
case 常量表达式1:语句1; break;
case 常量表达式2:语句2; break;
…
case 常量表达式n:语句n; break;
default :语句n+1;
}
Top of Page
第五章 循环控制
1. 几种循环语句
goto语句(现已很少使用)
while语句 先判断表达式后执行语句
do-while语句 先执行语句后判断表达式
for语句
2. Break语句和continue语句
Break语句用于跳出循环,continue用于结束本次循环。
Top of Page
第六章 数组
1. 一维数组
c规定只有静态存储(static)和外部存储(extern)数组才能初始化。给数组初始化时可以不指定数组长度。
2. 二维数组
3. 字符数组
部分字符串处理函数
puts(字符数组) 将一个字符串输出到终端。
gets(字符数组) 从终端输入一个字符串到字符数组,并且得到一个函数值,为该字符数组的首地址
strcat(字符数组1,字符数组2) 连接两个字符数组中的字符串,数组1必须足够大。
Strcpy(字符数组1,字符串2) 将字符串2拷贝到字符数组1中。
Strcmp(字符串1,字符串2) 比较字符串,相等返回0,字符串1>字符串2,返回正数,小于返回负数。
Strlen(字符数组) 求字符串长度。
Strlwr( 字符串) 将字符串中的大写字母转换成小写
Strupr( 字符串) 将字符串中的小写字母转换成大写
以上是一些比较常用的字符串处理函数。
Top of Page
第七章 函数
1. 关于形参和实参的说明
① 在函数被调用之前,形参不占内存
② 实参可以是常量、变量或表达式
③ 必须指定形参的类型
④ 实参与形参类型应一致
⑤ 实参对形参的数据传递是"值传递",即单向传递
2. 函数返回值
如果想让函数返回一个值,在函数中就要用return语句来获得,在定义函数时也要对函数值指定类型,如果不指定,默认返回整型。
3. 函数调用
1)注意在函数调用时实参和形参的个数、类型应一一对应。对实参表求值的顺序是不确定的,有的系统按自左至右,有的系统则按自右至左的顺序。这一点要注意。
2)函数调用的方式:函数语句,函数表达式,函数参数
3)如果主调函数和被调函数在同一文件中,并且主调函数在前,那么一般要在主调函数中对被调函数进行说明。除非:(1)被调函数的返回值类型为整型或字符型(2)被调函数出现在主调函数之前。
4)对函数的说明和定义是不同的,定义是指对函数功能的确立,包括指定函数名,函数值类型,形参及其类型、函数体等。说明则只是对已定义的函数返回值类型进行说明,只包括函数名、函数类型以及一个空的括弧,不包括形参和函数体。
5)c语言允许函数的递归调用(在调用一个函数的过程中又出现直接或间接的调用该函数本身)。
4. 数组作为函数参数
1)数组元素作为函数参数 和一般变量相同
2)
数
组
名作参数
应该
在主
调
和被
调
函数分
别
定
义
数
组
,形参数
组
的大小可以不定
义
。注意:数
组
名作参数,不是
单
向
传递
。
3)
多
维
数
组
作参数,在被
调
函数中
对
形参数
组
定
义时
可以省略第一
维
的大小
说
明,但不能省略第二
维
或更高
维
的
说
明。
5. 局部变量和全局变量
从变量作用域角度分,变量可分为局部变量和全局变量。
1)内部变量(局部变量)
在一个函数内定义,只在函数范围内有效的变量。
2)外部变量(全局变量)
在函数外定义,可以为本文件其它函数所共用,有效范围从定义变量的位置开始
到本文件结束。建议尽量少使用全局变量,因为它在程序全部执行过程中都占用
资源,而且使函数的通用性降低了。如果在定义外部变量之前的函数要想使用该
外部变量,则应在该函数中用extern作外部变量说明。
6. 动态存储变量与静态存储变量
从变量值存在的时间(生存期)角度来分,可分为静态存储变量和动态存储变量。
静
态
存
储
指在程序运行期
间给变
量分配固定的存
储
空
间
,
动态
存
储
指程序运行期
间
根据需要
动态
的
给变
量分配存
储
空
间
。
C语言中,变量的存储方法分为两大类:静态存储类和动态存储类,具体包括:
自
动
的(
auto
),静
态
的
(static)
,寄存器的
(register)
,外部的
(extern)
。
1) 局部变量的存储方式
函数中的局部变量如不作专门说明,都之auto的,即动态存储的,auto可以省略。局部变量也可以定义为static的,这时它在函数内值是不变的。静态局部变量如不赋初值,编译时系统自动赋值为0,动态局部变量如不赋初值,则它的值是个不确定的值。C规定,只有在定义全局变量和局部静态变量时才能对数组赋初值。为提高执行效率,c允许将局部变量值放在寄存器中,这种变量叫register变量,要用register说明。
但只有局部
动态变
量和形式参数可以作
为
register
变
量,其它不行。
2) 全局变量的存储方式
全局变量在函数外部定义,编译时分配在静态存储区,可以在程序中各个函数所引用。多个文件的情况如何引用全局变量呢?
假如在一个文件定
义
全局
变
量,在
别
的文件引用,就要在此文件中用
extern
对
全局
变
量
说
明,但
如果全局
变
量定
义时
用
static
的
话
,此全局
变
量就只能在本文件中引用了,而不能被其它文件引用。
3) 存储类别小结
从作用域角度分,有局部变量和全局变量
局部变量:自动变量,即动态局部变量(离开函数,值就消失)
静态局部变量(离开函数,值仍保留)
寄存器变量(离开函数,值就消失)
(形参可定义为自动变量和寄存器变量)
全局变量:静态全局变量(只限本文件引用)
全局变量(允许其它文件引用)
从存在的时间分,有静态存储和动态存储
动态存储:自动变量(本函数内有效)
寄存器变量(本函数内有效)
形参
静态存储:静态局部变量(函数内有效)
静态全局变量(本文件内有效)
全局变量(其它文件可引用)
从变量值存放的位置分
静态存储区:静态局部变量
静态全局变量
全局变量
动态存储区:自动变量和形参
寄存器内:寄存器变量
7. 内部函数和外部函数
内部函数:只能被本文件中的其它函数
调
用,定
义时
前加
static
,内部函数又称静
态
函数。
外部函数:可以被其它文件
调
用,定
义时
前加
extern
,如果省略,
则隐
含
为
外部函数,在需要
调
用此函数的文件中,一般要用
extern
说
明。
Top of Page
第八章 预编译处理
c编译系统在对程序进行通常的编译之前,先进行预处理。c提供的预处理功能主要有以下三种:
1)宏定义 2)文件包含 3)条件编译
1. 宏定义
不带参数的宏定义
用一个指定的标识符来代表一个字符串,形式:
#define
标识
符
字符串
几点说明:
1) 宏名一般用大写
2)
宏定
义
不作
语
法
检查
,只有在
编译
被宏展
开
后的源程序
时
才会
报错
3)
宏定
义
不是
c
语
句,不在行末加分号
4)
宏名有效范
围为
定
义
到本源文件
结
束
5)
可以用
#undef
命令
终
止宏定
义
的作用域
6) 在宏定义时,可以引用已定义的宏名
带参数的宏定义
定义形式:
#define
宏名(参数表) 字符串
这和函数有些类似,但他们是不同的:
1)
函数
调
用
时
,先求
实
参表达式
值
,再代入形参,而宏只是
简单
替
换
,并不求
值
2)
函数
调
用是在程序运行
时
分配内存的,而宏展
开时
并不分配内存,也没有返回
值
的概念
3) 对函数中的实参和形参都要定义类型,而且要求一致,宏名无类型,其参数也没有类型。
4) 函数只有一个返回值,而宏可以得到几个结果
5)
宏替
换
不占运行
时间
,只占
编译时间
,而函数
调
用占运行
时间
2. 文件包含处理
#include "
文件
1" 就是将文件1的全部内容复制插入到#include位置,
作
为
一个源文件
进
行
编译
。
在#include命令中,文件名可以用" "也可以用< >,假如现在file1.c中包含file2.h文件,
" "
表示系
统
先在
file1.c
所在目
录
中找
file2.h
,如果找不到,再按系
统
指定的
标
准方式
检
索目
录
,
< >
表示系
统
直接按指定的
标
准方式
检
索目
录
。
所以用
" "
保
险
一点。
3. 条件编译
条件
编译指不对整个程序都编译,而是编译满足条件的那部分。条件编译有以下几种形式:
1)
#ifdef
标识
符
程序段1
#else
程序段2
#endif
它的作用:当
标识
符在前面已
经
被定
义过
(一般用
#define),则对程序段1编译,否则对程序段2编译。
2)
#ifndef
标识
符
程序段1
#else
程序段2
#endif
它的作用和#ifdef相反,当标识符没被定义过,对程序段1编译,否则对程序段2编译。
3)
#if
表达式
程序段1
#else
程序段2
#endif
它的作用:当表达式值为真(非0)时,对程序段1编译,否则对程序段2编译。
Top of Page
第九章 指针
指针说白了就是地址。指针变量就是用来存放指针(地址)的变量。
1.
变
量的指
针
和指向
变
量的指
针变
量
读起来很拗口,说白了就是变量的地址和用来存放变量地址的地址变量。因为
一个
变
量在
编译
的
时
候系
统
要
为
它分配一个地址,假如再用一个变量来存放这个地址,那么这个变量就叫做指向变量的指针变量,也就是用来存放变量地址的这么一个变量。所谓"指向"就是指存放××的地址,如指向变量的指针变量,"指向"就是指用来存放变量的地址,再如指向数组的指针变量,"指向"就是指存放数组的地址。只要理解了这个,指针也就不难了。另外,还有指向字符串的指针变量,指向函数的指针变量,指向指针的指针变量等。
1) 指针变量的定义
形式:
类
型
标识
符
*
标识
符 如:int *pointer;
要注意两点:*表示pointer是个指针变量,在用这个变量的时候不能写成*pointer, *pointer是pointer指向的变量。
一个指
针变
量只能指向同一个
类
型的
变
量。如上面
pointer只能指向int型变量。
2)指针变量的引用
两个有关的运算符:
&
取地址运算符 &a 就代表变量a的地址
*
指
针
运算符 *a 就代表变量a的值
2. 数组的指针和指向数组的指针变量
数
组
的指
针
指数
组
的起始地址,数组元素的指针指数组元素的地址。
1)指向数组元素的指针变量的定义与赋值
定义和指向变量的指针变量定义相同,c规定数组名代表数组的首地址,即第一个数组元素地址。
2)通过指针引用数组元素
我们通常引用数组元素的形式是a[i],如果用指针可以这样引用,
*(a+i),或定义一个指针变量p,将数组a的首地址赋给p,p=a;然后用*(p+i)引用。
注意:指针变量p指向数组a首地址,
则
p++
指向数
组
a的下一元素地址,即
a[1]
的地址。
3)数组名作函数参数
形参数
组
和
实
参数
组
之
间
并不是
值传递
,而是共用同一段地址,所以在函数
调
用
过
程中如果形参的
值发
生
变
化,
则实
参的
值
也跟着
变
化。
4)指向多维数组的指针和指针变量
以二维数组为居多。假设定义了一个二维数组a[3][4],那么
a
代表整个二
维
数
组
的首地址,也代表第0行的首地址,同时也是第0行第0列的元素的首地址。
a +0
和
a[0]
代表第
0
行首地址,
a+1
和
a[1]
代表第一行的首地址。
假设a是一个数组的首地址,那么如果a是一维的,a+I代表第I个元素的地址,如果a是二维的,则a+I代表第I行的首地址。
那么
第一行第二列的元素地址如何表示呢?
a[1]+2
或
&a[1][2]
或
*(a+1)+2
。
我们只要记住:在二维数组中
a
代表整个数
组
的首地址,
a[I]
代表第
I
行的首地址,
a[I]
与
*(a+I)
等价就行了。只要运用熟练了就没什么复杂的了。
5)指向由m个整数组成的一维数组的指针变量
如:
int (*p)[4]
,
p
是一个指向包含
4
个元素的一
维
数
组
,如果p先指向a[0],则p+1指向a[1],即p的增值是以一维数组的长度为单位的,这里是4,举个例子:
假设a[3][4]={1,3,5,7,9,11,13,15,17,19,21,23},p先指向a[0]也就是数组a的首地址,那么p+1就是a[1]的首地址即元素9的地址,
因
为
在定
义
p
时
int (*p)[4]
,定
义
一
维
数
组长
度
为
4
,所以
p+1
就等于加了一个一
维
数
组
的
长
度
4
。
3. 字符串的指针和指向字符串的指针变量
1)字符串的表示形式
c中字符串有两种表示形式:一种是数组,一种是字符指针
char string[]="I love c!";
char *str="I love c!";
其
实
指
针
形式也是在内存中
开
辟了一个数
组
,只不
过
数
组
的首地址存放在字符指
针变
量
str
中,千万不要
认为
str
是一个字符串
变
量。
2)字符串指针作函数参数
实际上字符串指针就是数组的首地址。
3)字符指针变量与字符数组的区别
① 字符数组由若干元素组成,每个元素存放一个字符,而字符指针变量只存放字符串的首地址,不是整个字符串
②
对
数
组
初始化要用
static
,
对
指
针变
量不用。
③
对
字符数
组赋值
,只能
对
各个元素
赋值,不能象下面这样:
char str[14];
str="I love c!";
对指针变量可以,
char *str;
str="I love c!";
注意:此
时赋给
str
的不是字符,而是字符串首地址。
④
数
组
在定
义
和
编译时
分配内存
单
元,而指
针变
量定
义
后最好将其初始化,否
则
指
针变
量的
值
会指向一个不确定的内存段,将会破坏程序。如:
char *a;
scanf( "%s", a );这种方法是很危险的,应该这样:
char *a, str[10];
a = str;
scanf( "%s", a );这样字符指针就指向了一个确定的内存段。
⑤
指
针变
量的
值
是可以改
变
的,而字符数
组
名所代表的字符串首地址却是不能改
变
的。
4. 函数的指针和指向函数的指针变量
一个函数在
编译时
被分配一个入口地址,
这
个入口地址就称
为
函数的指
针
。函数名代表函数的入口地址,这一点和数组一样。我们可以用一个指针变量来存放这个入口地址,然后通过该指针变量调用函数。如:假设有一个求两者较大的函数如下:int max( int x, int y );
当我们调用这个函数时可以这样:
int c;
c=max( a, b );这是通常调用方法,其实我们可以定义一个函数指针,通过指针来调用,如:
int (*p)(); //
注意指向函数指
针变
量的定
义
形式
p=max; //
此句就是将函数的入口地址
赋给
函数指
针变
量
p
c=(*p)( a, b );
有些朋友可
能
对
(*p)()
不大理解,其
实
它的意思就是定
义
一个指向函数的指
针变
量
p
,
p
不是固定指向哪个函数的,而是
专门
用来存放函数入口地址
的
变
量。在程序中把哪个函数的入口地址赋给它,它就指向哪个函数。但要注意,p不能象指向变量的指针变量一样进行p++,p-等无意义的操作。
既然p是一个指针变量,那么就可以作为函数的参数进行传递。其实函数的指针变量最常用的用途之一就是作为函数参数传递到其它函数。这也是c语言中应用的比较深入的部分了。
5. 返回指针值的函数
我们知道,一个函数可以带回一个整型值、字符值、实型值等,函数还可以带回一个指针型的数据,即地址。这种函数的定义形式如下:
类
型
标识
符
*
函数名
(
参数表
) 如:int *a(x,y)返回一个指向整型的指针
使用这种函数的时候要注意:
在
调
用
时
要先定
义
一个适当的指
针
来接收函数的返回
值
。
这
个适当的指
针
其
类
型
应为
函数返回指
针
所指向的
类
型。
这样的函数比较难于理解,其实只要把它当做一般的函数来处理就容易了。当我们觉得指针难于理解的时候,就把它暂时当做整型来看,就好理解多了。
6. 指针数组
指
针
数
组
无疑就是数
组
元素
为
指
针
,定
义
形式
为
:
类
型
标识
*
数
组
名
[
数
组长
度
]
如
:int *p[4]
,千万不要写成
int (*p)[4]
,
这
是指向一
维
数
组
的指
针变
量。指
针
数
组
多用于存放若干个字符串的首地址,注意一点,在定义指针数组时初始化,如下:
static char *name[]={"Li jing","Wang mi","Xu shang"};
不要以为数组中存放的是字符串,它存放的是字符串首地址,这一点一定要注意。
7.
指向指
针
的指
针
说的明白一点,将一个指针再用一个变量来存放,那么这个变量就是指向指针的指针。定义如:char * *p;
8.
指
针
数
组
作
main()
函数的参数
函数形式为
main( int argc, char *argv[] ){}
main函数的
参数是从命令行得到的,
argc
指命令行参数个数,
注意命令名也算一个参数,命令行参数都是字符串,他
们
的首地址构成一个指
针
数
组
argv
。Main函数的形参用argc和argv只是一个习惯,也可以定义成别的名字。
9. 指针小结
1)有关指针的数据类型
定 义 含 义
Int I; 定义一个整型变量I
Int *p; P为指向整型数据的指针变量
Int a[n]; 定义整型数组a,它有n个元素
Int *p[n]; 定义指针数组p,它有n个指向整型的指针元素
Int (*p)[n]; P为指向含有n个元素的一维数组的指针变量
Int f(); F为返回整型值的函数
Int *p(); P为返回值为指针的函数,该指针指向整型数据
Int (*p)(); P为指向函数的指针,该函数返回一个整型值
Int **p; 定义一个指向指针的指针变量
2)ANSI新增了一种
void *
指
针类
型,即定
义
一个指
针变
量,但不指向任何数据
类
型,等用到的
时
候再
强
制
转换类
型。如:
char *p1;
void *p2;
p1 = (char *)p2;
也可以将一个函数定义成void *型,如:
void *fun( ch1, ch2 )
表示函数fun返回一个地址,它指向空类型,如果需要用到此地址,也要对其强制转换。如(假设p1为char型):
p1=(char *)fun( c1,c2 );
指针应该说是c语言中比较重要的概念,也是c语言的精华,它有很多优点,但用不好也会带来严重性的错误,这就需要我们多用,多练,慢慢的积累经验。
Top of Page
第十章 结构体与共用体
1. 定义
结构体定义的一般形式:
struct
结
构体名
{
成
员
列表
};
定义一个结构体变量可以这样定义:
struct
结
构体名
结
构体
变
量名
;
2. 结构体变量的引用
在引用结构体变量时应注意以下规则:
1)不能将结构体变量作为一个整体输入输出,只能对变量当中的各个成员输入输出
。新
标
准
C
允
许
将一个
结
构体
变
量直接
赋值给
另一个具有相同
结
构的
结
构体
变
量。
3. 结构体变量的初始化
如:
struct student
{long int num;
char name[20];
char sex;
char addr[20];
}a={89031,"Li Lin",'M',"123 Beijing Road" };
4. 结构体数组
struct student stu[4];
定义了一个数组stu,其元素为struct student类型,数组有4个元素。注意数组各元素在内存中是连续存放的。
在定义结构体数组时,数组元素个数可以不指定。编译时,系统会根据给出初值的结构体常量的个数来确定数组元素的个数。
5. 指向结构体变量的指针
因
为结
构体
变
量在内存中是
连续
存放各成
员
的,因此我
们
可以将
结
构体
变
量在内存中的起始地址存放到一个
变
量中,那
么这
个
变
量就是指向
结
构体
变
量的指
针
。
注意将结构体变量的首地址赋给指针变量的形式:
struct student stu_1;
struct student *p;
p=&stu_1; //
要加取地址符 而指向函数和指向字符串的指
针
不用
在对引用结构体变量中的成员时,
有三
种
方式:
以上面的
结
构体
为
例:
设
p
为
指向此
结
构体
变
量的指
针
,即
p=&a;
1) a.num
2) (*p).num
3) p->num
6. 指向结构体数组的指针
struct student *p;
struct student stu[4];
p=stu;
则p为
指向
结
构体数
组
的指
针变
量。这里应注意p++,p指向stu[0],p++则指向stu[1]。P指向的是数组中一个元素的首地址,
而不能
让
p
指向元素中的某一成
员
,如
p=&stu[I].name
是不
对
的。
7. 用指向结构体的指针作函数参数
虽然
ANSI C
允
许
用整个
结
构体作
为
函数参数,但要将全部成
员值
一个一个
传递
,
开销
大。所以用指
针
作参数,能提高运行效率。
Struct student stu;
用整个结构体作为参数调用形式:
fun( stu );
而且被调函数fun中也要定义成结构体变量,struct student stu;
用指
针
作参数
调
用形式:
fun( &stu );
被调函数fun中定义成指针变量,
struct student *p;
8. 用指针处理链表
链表是一种重要的数据结构,原因就在于它可以动态的进行存储分配。链表都有一个头指针,用来存放整个链表的首地址。链表的定义形式如下:
struct node{
int num;
…
struct node *next;
};
next
用来存放下一
节
点的地址。
如何进行动态的开辟和释放存储单元呢?c提供了以下有关函数:
1)
malloc(size)
在内存的
动态
存
储
区
开
辟一个
长
度
为
size
的
连续
空
间
。成功返回空
间
首地址,失
败
返回
0;
2)
calloc(n,size)
在内存的
动态
存
储
区
开
辟
n
个
长
度
为
size
的
连续
空
间
。成功返回空
间
首地址,失
败
返回
0;
3)
free(ptr)
释
放由
ptr
指向的内存区。
Ptr
是最近
调
用一次
调
用
malloc
和
calloc
时
返回的
值。
上面函数中,n和size为整型,ptr为字符指针。
9. 共用体
定义形式:
union
共用体名
{
成
员
列表
}
变
量列表
;
共用体和结构体类似,只是有一点不同,结构体中个成员的起始地址不同,结构体变量在内存中的长度为各成员长度之和;而共用体中个成员的起始地址相同,共用体变量所占的内存长度为最长的成员的长度。
共用体类型数据的特点:
1) 同一个内存段可以存放几种不同类型的成员
2)
共用体
变
量中起作用的成
员
是最后一次存放的成
员
3)
不能
对
共用体
变
量名
赋值
,不能在定
义时
初始化。
4)
不能把共用体
变
量作
为
函数参数
5) 共用体类型可以出现在结构体定义中,反之也可,也可以定义共用体数组。
另外,结构体名可以作为参数,而共用体名不可以。
这两中数据结构在不同场合中各有所用。
10. 枚举类型
定义形式如下:举个例子
enum weekday{sun,mon,tue,wed,thu,fri,sat};
enum weekday workday,week_end; //
定
义
枚
举变量
workday和week_end被定义成枚举类型,
他
们
的
值
只能
为
sun
到
sat
之一。
也可以直接定义枚举变量,这一点与结构体相同
enum weekday{sun,mon,tue,wed,thu,fri,sat}wordday,week_end;
注意:
枚
举
元素是作
为
常量存在的,他
们
是有
值
的,
c
在
编译时
使他
们
的
值
按
顺
序
为
0,1,2…
如:上面的定义中,sun的值为0,mon的值为1
另外:
虽
然枚
举
元素有
值
,但不能将一个整数直接
赋给
一个枚
举变
量。
应进
行
强
制
类
型
转换
,如:
workday=(enum weekday)2;
它相当于把
tue
赋给
了
workday
。
11. 用typedef定义类型
typedef
的作用就是能
够让
你定
义
一个自己喜
欢
的数据
类
型名来代替已有的数据
类
型名。如:
typedef int INT;那么我就可以用INT来定义整型变量了。作用和int一样。
Typedef
用于
结
构体定
义
,如:
Typedef struct{
Int day;
Int month;
Int year;
}DATE;
DATE birthday;
DATE *p;等等
用
typedef
有利于程序的通用与移植。
Top of Page
第十一章 位运算
1)概述
所谓位运算是指进行二进制位的运算。在系统软件中,常要处理二进制位的问题。
c提供的位运算符有:
& 按位与
| 按位或
^ 按位异或
~ 取反
<< 左移
>> 右移
&
对
于将一个
单
元清零、取一个数中的某些指定位以及保留指定位有很大用途。
|
常被用来将一个数的某些位置
1
。
^
判断两个位
值
,不同
为
1
,相同
为
0
。常用来使特定位翻
转
等。
~
常用来配合其它位运算符使用的,常用来
设
置屏蔽字。
<<将一个数的各二进制位全部左移,高位左移后溢出,舍弃不起作用。
左移一位相当于
该
数乘
2
,左移
n
位相当于乘
2n
。左移比乘法运算要快的多。
>>右移时,要注意符号问题。对无符号数,右移时左边高位移入0,对于有符号数,如果原来符号位为0(正数),则左边移入0;如果符号位为1(负数),则左边移入0还是1要取决于系统。移入0的称为"逻辑右移",移入1的称为"算数右移"。
2)位段
将一个字
节
分
为
几段来存放几个信息。所
谓
位段是以位
为单
位定
义长
度的
结
构体
类
型中的成
员
。如:
struct packed-data{
unsigned a:2;
unsigned b:6;
unsigned c:4;
unsigned d:4;
int I;
}data;
其中
a,b,c,d
分
别
占
2
位
,6
位
,4
位
,4
位。
I
为
整型,占
4
个字
节
。
对于位段成员的引用如下:
data.a = 2;
等,但要注意
赋值时
,不要超出位段定
义
的范
围
。如位段成
员
a
定
义为
2
位,最大
值为
3
,即
(11)2
,所以
data.a=5;
就会取
5
的两个低位
进
行
赋值
,就得不到想要的
值
了。
关于位段的定义和引用,有几点重要说明:
①
若某一个段要从另一个字
开
始存放,可以定
义
:
unsigned a:1;
unsigned b:2;
unsigned :0;
unsigned c:3; (
另一
单
元
)
使用
长
度
为
0
的位段,作用就是使下一个位段从下一个存
储单
元
开
始存放。
②一个位段必须存放在用一个存储单元中,不能跨两个单元。
③
可以定
义
无名位段。如:
unsigned a:1;
unsigned :2; (这两位空间不用)
unsigned b:3;
④
位段的
长
度不能大于存
储单
元的
长
度,也不能定
义
位段数
组。
Top of Page
第十二章 文件
1) 概述
c
语
言将文件看成一个字符的序列,分
为
ASCII
文件(文本文件)和二
进
制文件。即一个
c
文件就是一个字
节
流或二
进
制流。
ASCII文件每一个字节放一个ASCII码,代表一个字符,输出与字符一一对应,便于逐个处理字符,但占用空间较多。二进制文件按内存中的存储形式原样输出到磁盘上,节省空间,由于输出与字符不对应,不能直接输出字符形式,一般用于保存中间结果。目前c对文件的处理只有缓冲文件系统一种方法,即
无
论
是从程序到磁
盘
文件
还
是从磁
盘
文件到程序,数据都要先
经过缓
冲区,待
缓
冲区充
满
后,才集中
发
送。
2) 文件夹类型指针
在缓冲文件系统中,关键的概念是
文件指
针。因为每个被使用的文件都在内存中开辟一个缓冲区,来存放文件有关信息。这些信息保存在一个结构体变量中,该
结
构体
类
型是由系
统
定
义
的,取名
为
FILE
,在
stdio.h
中定
义
。
FILE *fp;
定
义
了一个文件指
针变
量
fp
,以后
对
文件的操作都是通
过
fp
进
行的。
3) 文件的打开与关闭
在对文件读写之前,要先打开文件。
打开文件的函数为:fopen(),调用方式为:
FILE *fp;
fp=fopen( filename,
使用文件方式
);
fopen()
失
败
返回一个空指
针
NULL
,成功
则
返回一个指向
"filename"
的文件指
针
,
赋给
fp
,
这样
fp
就和打
开
的文件
联
系在一起了。或者
说
,
fp
指向了
"filename"
。
文件使用方式:
r,w,a,rb,wb,ab,r+,w+,a+,rb+,wb+,ab+,具体含义要记住。
4)文件的关闭
为
了防止数据
丢
失,程序
结
束前,
务
必将打
开
的文件
关闭
,即将文件指
针
与文件脱
钩
。用
fclose(
文件指
针
)
函数
关闭
文件,
执
行函数后,先将
缓
冲区中的数据送到磁
盘
文件,然后
释
放文件指
针
。成功返回
0
,失
败
返回非
0
。
5)文件的读写
文件打开后,就可以对其读写了,常用的文件读写函数有:
①fputc和fgetc
fputc
将一个字符写到文件,形式
为
fputc( ch, fp );
将字符
ch
写入
fp
所指向的文件。成功返回
该
字符,失
败
返回
EOF
,
EOF
在
stdio.h
中定
义为
符号常量
-1
。
fgetc
从指定文件
读
入一个字符,
该
文件必
须
是以
读
或
读
写方式打
开
的。
调
用形式
为
ch=fgetc(fp);
从
fp
指向的文件
读
入一个字符
赋给
ch
,当文件
结
束
时
,
fgetc
返回一个
EOF
,我们可以用函数
feof(fp)
来判断是否已到文件尾,返回1表示已到文件尾,否则返回0。这个函数适用于文本文件和二进制文件。
②fread和fwrite函数
可以
读
写一
组
数据。
调
用形式如下:
fread( buffer, size, count, fp );
fwrite( buffer, size, count, fp );
buffer为一个指针,对fread来讲,是指从文件读出数据的存放地址,对fwrite来讲,是要写入文件的数据的地址。
size 要读写的字节数
count 要进行读写多少个size字节的数据项(书上这么说)其实就是读写的次数
fp 文件指针
这两个函数返回值成功为1,失败为非1,一般用于二进制文件的读写。
注意:有些c编译系统不具备这两个函数。
③fprintf()和fscanf()函数
格式化输出和输入函数,与printf()和scanf()作用相似,只有一点不同
,
fprintf()
和
fscanf()
的
读
写
对
象不是
终
端而是磁
盘
文件。
调
用方式:
fprintf(文件指针,格式字符串,输出列表);
fscanf(文件指针,格式字符串,输出列表);
④fgets()和fputs()函数
作用是读写一个字符串,如:
fgets(str,n,fp);
意为从fp指向的文件读出n-1个字符,存放到str中,成功返回str的首地址。
fputs( "China", fp );
把字符串China写入fp指向的文件。成功返回0,失败为非0。
6)文件的定位
文件中有一个位置指
针
,指向当前
读
写的位置,如果要
强
制改
变
位置指
针
的位置,可以用有
关
函数:
①
rewind
使位置指
针
重新返回文件的
开头
②fseek()
fseek()
函数可以任意改
变
位置指
针
的位置,以
实现
随机
读
写文件。
调
用形式:
fseek(
文件指
针类
型
,
位移量
,
起始点
);
起始点有以下三个值:
SEEK_SET或0 文件开始
SEEK_CUR或1 文件当前位置
SEEK_END或2 文件末尾
位移量指以起始点为基点,
移
动
的字
节
数(正数向文件尾移
动
,
负
数向文件
头
移
动
),一般位移量用
long
型数据,以避免大于
64K
的文件出
错
。Fseek()函数一般用于
二
进
制文件,因为文本文件要进行字符转换,计算时会发生混乱。
Fseek( fp, 100L, 0 ); 将位置指针从文件头向文件尾移动100个字节处。
Fseek( fp, 50L, 1 );
将指
针
从当前位置向文件尾移
动
50
个字
节处
。
Fseek( fp, -10L, 2 ); 将指针从文件尾向文件头移动10个字节处。
③
ftell()
得到流式文件位置指
针
的当前位置,成功返回相
对
于文件
头
的
位移量
,失
败
返回
-1L
。
Top of Page
另外,由于ANSI C不使用非缓冲文件系统,而其它C系统还用到非缓冲文件系统,所以对于这一章节只是略微的看了一下,不至于以后见到这样的程序不认识,呵呵。这一节主要讲了几个文件读写的有关函数,看了也没做笔记。如果关心的话,自己看一下吧。
至此,这本语言类的基础书又温习了一遍,由于工作太忙,花了我半个月的时间。不过总的来说,收获还是很大的,有很多以前没有发现的新东西,也有很多以前理解较浅显的东西,这次加深了理解。其实,看完了一章后,最好将书后的习题一一解答,因为这是对这章知识点的考查。同时,动手编一下小程序,也能提高自己的编程能力。
今天把这篇简单的笔记拿出来供朋友们参考,如果有什么不准确的地方,真诚欢迎指正!