通知
关于网站更多信息请加whimurmur模板/jpress插件QQ群(1061691290)            网站从因情语写改为晴雨            这个网站的模板也从calmlog_ex改为 whimurmur

C语言程序设计学习笔记

228人浏览 / 0人评论 / | 作者:whisper  | 分类: C/C++  | 标签: c++ grammar  | 

作者:whisper

链接:https://www.proprogrammar.com/article/923

声明:请尊重原作者的劳动,如需转载请注明出处


来源:中国大学MOOC

链接:https://www.icourse163.org/course/ZJU-9001

教师:翁恺


第一周 程序设计与C语言

程序是⽤用特殊的编程语⾔言写出来表达如何解决问题的

不是⽤用编程语⾔言来和计算机交谈(交流),⽽而是描述要求它如何做事情的过程或方法(命令)

计算机做的所有的事情都叫做计算

C语言

• C语⾔是从B语⾔发展⽽来的,B语⾔是从BCPL发展⽽ 来的,BCPL是从 FORTRAN发展⽽来的

• BCPL和B都⽀持指针间接⽅式,所以C也⽀持了

• C语⾔还受到了PL/1的影响,还和PDP-11的机器语⾔ 有很⼤的关系

• 1973年3⽉,第三版的Unix上出现了C语⾔的编译器

• 1973年11⽉,第四版的Unix(System Four)发布了, 这个版本是完全⽤C语⾔重新写的

C的发展与版本-K&R

经典 C

• ⼜被叫做 “K&R the C”

• The C Programming Language, by Brian Kernighan and Dennis Ritchie, 2nd Edition, Prentice Hall

C的发展与版本-标准

• 1989年ANSI发布了⼀个标准——ANSI C

• 1990年ISO接受了ANSI的标准——C89

• C的标准在1995年和1999年两次更新—— C95和C99

• 所有的当代编译器都⽀持C99了

C语⾔⽤在哪⾥?

• 操作系统

• 嵌⼊式系统

• 驱动程序

• 底层驱动

    • 图形引擎、图像处理、声⾳效果

C是⼀种⼯业语⾔

编译—>运⾏

• C需要被编译才能运⾏,

所以你需要

• 编辑器

• 编译器

• 或者,IDE(集成开发环境)

推荐的编程软件

• Dev C++(4.9 for Win7, 5.0 for Win8)

• 免费

• 安装简单

• 不⽤建⼯程

其他选择

• MS Visual Studio Express(Windows)

• Xcode(Mac OS X)

• Eclipse-CDT

• Geany(和MinGW⼀起)

• Sublime(和MinGW⼀起)

• vim/emacs(和MinGW⼀起)

第⼀个C程序

#include <stdio.h>

int main()
{
    printf("Hello World!\n");

    return 0;
}

不要⽤中⽂!!!

• 中国学⽣还有⼀个极其常⻅的低级错误,就是 ⽤了中⽂输⼊法来输⼊程序。那些标点符号, 在中⽂和英⽂可能看上去相似,但是对于计算 机是完全不同的符号,如果你还开了全⾓标点 的话,问题就更严重了

四则运算

乘除与原先不同,多了个取余,括号用来改变运算顺序

第二周 计算

变量

• int price = 0;

• 这⼀⾏,定义了⼀个变量。变量的名字是 price,类型是int,初始值是0。

• 变量是⼀个保存数据的地⽅,当我们需要 在程序⾥保存数据时,⽐如上⾯的例⼦中 要记录⽤户输⼊的价格,就需要⼀个变量 来保存它。⽤⼀个变量保存了数据,它才 能参加到后⾯的计算中,⽐如计算找零。

变量定义,变量声明,变量初始化,变量类型,变量名,初始化

变量的名字

• 变量需要⼀个名字,变量的名字是⼀种 “标识符”,意思是它是⽤来识别这个和那 个的不同的名字。

• 标识符有标识符的构造规则。基本的原则 是:标识符只能由字⺟、数字和下划线组 成,数字不可以出现在第⼀个位置上,C 语⾔的关键字(有的地⽅叫它们保留字) 不可以⽤做标识符。

C语⾔的保留字

auto,break,case,char,const, continue,default,do,double, else,enum,extern,float,for, goto,if,int,long,register,return, short,signed,sizeof,static, struct,switch,typedef,union, unsigned,void,volatile,while, inline,restrict

赋值和初始化

• int price = 0;

• 这⼀⾏,定义了⼀个变量。变量的名字是 price,类型是int,初始值是0。

• price=0是⼀个式⼦,这⾥的“=”是⼀个赋 值运算符,表⽰将“=”右边的值赋给左边 的变量。

变量初始化

表达式

• “=”是赋值运算符,有运算符的式⼦就叫 做表达式。

    • price=0;

    • change=100-price;

变量类型

• int price = 0;

• 这⼀⾏,定义了⼀个变量。变量的名字是 price,类型是int,初始值是0。

• C是⼀种有类型的语⾔,所有的变量在使 ⽤之前必须定义或声明,所有的变量必须 具有确定的数据类型。数据类型表⽰在变 量中可以存放什么样的数据,变量中只能 存放指定类型的数据,程序运⾏过程中也 不能改变变量的类型。

常量

固定不变的数,是常数。直接写在程序⾥, 我们称作直接量(literal)。

用const定义常量

tips

程序要求读⼊多个数字时,可以在⼀⾏输 ⼊,中间⽤空格分开,也可以在多⾏输⼊

在scanf的格式字符串中有⼏个%d,它就等 待⽤户输⼊⼀个整数,当然,字符串后⾯ 也需要对应有那么多个整数

浮点数

带⼩数点的数值。浮点这个词的本意就是 指⼩数点是浮动的,是计算机内部表达⾮ 整数(包含分数和⽆理数)的⼀种⽅式。 另⼀种⽅式叫做定点数,不过在C语⾔中 你不会遇到定点数。⼈们借⽤浮点数这个 词来表达所有的带⼩数点的数。

整数

整数类型不能表达有⼩数部分的数,整数 和整数的运算结果还是整数。计算机⾥会 有纯粹的整数这种奇怪的东⻄,是因为整 数的运算⽐较快,⽽且占地⽅也⼩。其实 ⼈们⽇常⽣活中⼤量做的还是纯粹整数的 计算,所以整数的⽤处还是很⼤的。

表达式计算

⼀个表达式是⼀系列运算符和算⼦的组合, ⽤来计算⼀个值

amount = x * (1 + 0.033) * (1
+ 0.033) * (1 + 0.033);
total = 57;
count = count + 1;
value = (min / 2) * lastValue;

运算符

• 运算符(operator)是指进⾏运算的动 作,⽐如加法运算符“+”,减法运算符 “-”。

• 算⼦(operand)是指参与运算的值,这 个值可能是常数,也可能是变量,还可能 是⼀个⽅法的返回值

运算符优先级

单目运算符 双目运算符 三目运算符

赋值运算符 嵌⼊式赋值

结合关系

一般自左向右,单目时自右向左

递增递减运算符 ++ --

前缀后缀 i++ i-- ++i --i

第三周 判断

判断

if ( 条件成⽴ ) {
…
}

条件

计算两个值之间的关系,所以叫做关系运算

所有的关系运算符的优先级⽐算术运算的 低,但是⽐赋值运算的⾼

判断是否相等的==和!=的优先级⽐其他的 低,⽽连续的关系运算是从左到右进⾏的

注释

单行注释 // 多行注释 /* */

否则的话

else

几种形式

if(xxx){

}


if(xxx){

}else{

}


if(xxx){

}else if(xxx){

}else{

}


if(xxx){

}else if(xxxx){

}...
...
}else{

}

分支

嵌套的if

级联的if-else if

if语句常⻅错误

• 忘了⼤括号

• if后⾯的分号

• 错误使⽤==和=

• 使⼈困惑的else

多路分⽀

switch-case case break default

第四周 循环

循环

while循环 先判断,再循环

do-while循环 先循环,再判断(至少循环一次)

第五周 循环控制

for循环

可以被while循环相互代替

循环迭代(对比递归)

for ( 初始动作; 条件; 每轮的动作 ) { }

• for中的每⼀个表达式都是可以省略的

for (; 条件; ) == while ( 条件 )

• 如果有固定次数,⽤for

• 如果必须执⾏⼀次,⽤do_while

• 其他情况⽤while

循环控制

break; continue

跳出多重循环 用标志变量 用goto

嵌套的循环

for(){

    for(){


    }
}

第六周 数据类型

C是有类型的语⾔

C语⾔的变量,必须:

• 在使⽤前定义,并且

• 确定类型

类型安全

C语⾔需要类型,但是对类型的安全检查并不⾜够

• 整数

    • char、short、int、long、long long

• 浮点数     • float、double、long double

• 逻辑

    • bool

• 指针

• ⾃定义类型

    枚举 结构体 共同体

sizeof

是⼀个运算符,给出某个类型或变量在内存中所占据 的字节数

• sizeof(int)

• 是静态运算符,它的结果在编译时刻就决定了

• 不要在sizeof的括号⾥做运算,这些运算不会做的

正数 负数 有符号 无符号 整数 浮点数 字符 字符串 逻辑 指针 数组 自定义类型 void

整数

• char:1字节(8⽐特)

• short:2字节

• int:取决于编译器(CPU),通常的意义是“1个字”

• long:取决于编译器(CPU),通常的意义是“1个字”

• long long:8字节

整数

整数的范围

• char:1字节:-128 ~ 127

• short:2字节:-32768 ~ 32767

• int:取决于编译器(CPU),通常的意义是“1个字”

• long:4字节

• long long:8字节

整数越界

8进制和16进制 0nn 0xnn

浮点数

科学计数法

超过范围的浮点数

• printf输出inf表⽰超过范围的浮点数:±∞

• printf输出nan表⽰不存在的浮点数

浮点数的内部表达 IEEE754

字符

字符与int通用,0~255的int

逃逸字符(escape char) /

逻辑类型

与int通用 0,1的int

类型转换

小->大 自动类型转换

大->小 强制类型转换

逻辑运算

• 逻辑运算是对逻辑量进⾏的运算,结果只 有0或1

• 逻辑量是关系运算或逻辑运算的结果

运算符优先级

括号 > 单目 > 算术 > 关系 > 逻辑 > 赋值

短路运算符 || &&

非短路  | &

逗号运算符 三目运算符:  , ?:

第七周 函数

函数

函数是⼀块代码,接收零个或多个参数, 做⼀件事情,并返回零个或⼀个值

函数定义

调⽤函数

函数名(参数值);

()起到了表⽰函数调⽤的重要作⽤   即使没有参数也需要()

函数返回

有返回值 无返回值

参数与变量

函数先后关系

C的编译器⾃上⽽下顺序分析你的代码

函数原型

函数头,以分号“;”结尾,就构成了函数的原型

函数原型的目的是告诉编译器这个函数⻓什么样

原型⾥可以不写参数的名字,但是⼀般仍然写上

类型不匹配

调⽤函数时给的值与参数的类型不匹配是C语⾔传统 上最⼤的漏洞

传值

过去,对于函数参数表中的参数,叫做“形式参 数”,调⽤函数时给的值,叫做“实际参数”

本地变量

函数的每次运⾏,就产⽣了⼀个独⽴的变量空间,在 这个空间中的变量,是函数的这次运⾏所独有的,称 作本地变量

定义在函数内部的变量就是本地变量

参数也是本地变量

变量的⽣存期和作⽤域

• ⽣存期:什么时候这个变量开始出现了,到什么时候 它消亡了

• 作⽤域:在(代码的)什么范围内可以访问这个变量 (这个变量可以起作⽤)

• 对于本地变量,这两个问题的答案是统⼀的:⼤括号 内——块

没有参数时

void f(void);

void f(); 在传统C中,它表⽰f函数的参数表未知,并不表⽰ 没有参数

逗号运算符

调⽤函数时的逗号和逗号运算符怎么区分?

调⽤函数时的圆括号⾥的逗号是标点符号,不是运算 符

f(a,b)    f((a,b))

函数⾥的函数

C语⾔不允许函数嵌套定义

第八周 数组

数组

定义数组

<类型>变量名称[元素数量];

int grades[100];

元素数量必须是整数

C99之前:元素数量必须是编译时刻确定的字⾯量

是⼀种容器(放东⻄的东⻄),特点是:

• 其中所有的元素具有相同的数据类型;

• ⼀旦创建,不能改变⼤⼩

• *(数组中的元素在内存中是连续依次排列的)

数组索引 从0开始 数组越界

int a[0]; 可以存在,但是⽆⽤

数组运算

数组的集成初始化

• 直接⽤⼤括号给出数组的所有元素的初始值

• 不需要给出数组的⼤⼩,编译器替你数数

• 如果给出了数组的⼤⼩,但是后⾯的初始值数量不⾜, 则其后的元素被初始化为0

集成初始化时的定位

• ⽤[n]在初始化数据中给出定位

• 没有定位的数据接在前⾯的位置后⾯

• 其他位置的值补零

• 也可以不给出数组⼤⼩,让编译器算

• 特别适合初始数据稀疏的数组

数组的⼤⼩

• sizeof给出整个数组所占据的内容的⼤⼩,单位是字 节

• sizeof(a[0])给出数组中单个元素的⼤⼩,于是相除就 得到了数组的单元个数

• 这样的代码,⼀旦修改数组中初始的数据,不需要修 改遍历的代码

数组的赋值

⼆维数组

int arr[a][b]

通常理解为arr是⼀个a⾏b列的矩阵

⼆维数组的遍历

⼆维数组的初始化

• 列数是必须给出的,⾏数可以由编译器来数

• 每⾏⼀个{},逗号分隔

• 最后的逗号可以存在,有古⽼的传统

• 如果省略,表⽰补零

• 也可以⽤定位

第九周 指针

指针

取地址运算 运算符 &

试试这些&

• 变量的地址

• 相邻的变量的地址

• &的结果的sizeof

• 数组的地址

• 数组单元的地址

• 相邻的数组单元的地址

&不能对没有地址的东⻄取地址

指针就是保存地址的变量

int i;

int* p = &i;

int* p,q;

int *p,q;

变量的值是内存的地址

• 普通变量的值是实际的值

• 指针变量的值是具有实际值的变量的地址

作为参数的指针

在被调⽤的时候得到了某个变量的地址:

在函数⾥⾯可以通过这个指针访问外⾯的这个变量

值传递 引用传递

访问那个地址上的变量*

*是⼀个单目运算符,⽤来访问指针的值所表⽰的地 址上的变量

*左值之所以叫左值

是因为出现在赋值号左边的不是变量,⽽是值,是表 达式计算的结果:

• a[0] = 2;

• *p = 3;

是特殊的值,所以叫做左值

指针的运算符&*

互相反作⽤ *&var  &*ptr

指针最常⻅的错误

定义了指针变量,还没有指向任何变量,就开始使⽤ 指针

指针与数组

函数参数数组实际上是一个指针,void f(int[] a) => sizeof(a) == sizeof(int*) == 4 而不是数组的字节数

数组变量是特殊的指针

指针与const

int i;

const int* p1 = &i;   (1)

int const* p2 = &i;   (2)

int *const p3 = &i;   (3)

(1)(2)表示指针变量的值不能变,但可以指向其它地址

(3)表示指针变量的值可以变,但不可以指向其它地址

主要看const与*的位置

const数组

数组每个值都不能改变

指针计算

指针是可计算的

给⼀个指针加1表⽰要让指针指向下⼀个 变量

int a[10];

int *p = a;

*(p+1) —> a[1]

如果指针不是指向⼀⽚连续分配的空间, 如数组,则这种运算没有意义

指针计算

这些算术运算可以对指针做:

• 给指针加、减⼀个整数(+, +=, -, -=)

• 递增递减(++/—)

• 两个指针相减

*p++ 先自增再取值

0地址

可以⽤0地址来表⽰特殊的事情:

• 返回的指针是⽆效的

• 指针没有被真正初始化(先初始化为0)

指针的类型

• ⽆论指向什么类型,所有的指针的⼤⼩都 是⼀样的,因为都是地址

• 但是指向不同类型的指针是不能直接互相 赋值的

• 这是为了避免⽤错指针

指针的类型转换

void* 表⽰不知道指向什么东⻄的指针

指针也可以转换类型

• int *p = &i; void*q = (void*)p

⽤指针来做什么

• 需要传⼊较⼤的数据时⽤作参数

• 传⼊数组后对数组做操作

• 函数返回不⽌⼀个结果 • 需要⽤函数来修改不⽌⼀个变量

• 动态申请的内存...

动态内存分配

malloc

#include void* malloc(size_t size);

• 向malloc申请的空间的⼤⼩是以字节为单位 的

• 返回的结果是void*,需要类型转换为⾃⼰ 需要的类型

• (int*)malloc(n*sizeof(int))

没空间了

如果申请失败则返回0,或者叫做NULL

free()

• 把申请得来的空间还给“系统”

• 申请过的空间,最终都应该要还 • 混出来的,迟早都是要还的

• 只能还申请来的空间的⾸地址

• free(0)?

函数间传递指针

函数返回指针

函数返回数组

第十周 字符串

字符串

字符数组

以0(整数0)结尾的⼀串字符

0或’\0’是⼀样的,但是和’0’不同

0标志字符串的结束,但它不是字符串的⼀部分

计算字符串⻓度的时候不包含这个0

字符串以数组的形式存在,以数组或指针的形式访问

更多的是以指针的形式

string.h ⾥有很多处理字符串的函数

字符串变量 字符串常量

⾏末的\表⽰下⼀⾏还是这个字符串常量

数组:这个字符串在这⾥

指针:这个字符串不知道在哪⾥

字符串可以表达为char*的形式

char*不⼀定是字符串

本意是指向字符的指针,可能指向的是字符 的数组(就像int*⼀样)

只有它所指的字符数组有结尾的0,才能说它 所指的是字符串

字符串运算

空字符串

char buffer[100]=””;

• 这是⼀个空的字符串,buffer[0] == ‘\0’

char buffer[] = “”;

• 这个数组的⻓度只有1!

字符串数组

char **a  char a[][]  char a[][10]  char *a[]

字符串函数

putchar

• int putchar(int c);

• 向标准输出写⼀个字符

• 返回写了⼏个字符,EOF(-1)表⽰写失败

getchar

• int getchar(void);

• 从标准输⼊读⼊⼀个字符

• 返回类型是int是为了返回EOF(-1)

标准库中的字符串函数

string.h

• strlen • strcmp • strcpy • strcat • strchr • strstr

char * strcpy(char *restrict dst, const char *restrict src);

安全版本

• char * strncpy(char *restrict dst, const char *restrict src, size_t n);

• char * strncat(char *restrict s1, const char *restrict s2, size_t n);

• int strncmp(const char *s1, const char *s2, size_t n);

字符串中找字符

• char * strchr(const char *s, int c);

• char * strrchr(const char *s, int c); • 返回NULL表⽰没有找到

字符串中找字符串

• char * strstr(const char *s1, const char *s2);

• char * strcasestr(const char *s1, const char *s2);

第十一周 结构类型

枚举

常量符号化

⽤枚举⽽不是定义独⽴的const int变量

• 枚举是⼀种⽤户定义的数据类型,它⽤关键字 enum 以如下语 法来声明:

enum 枚举类型名字 {名字0, …, 名字n} ;

• 枚举类型名字通常并不真的使⽤,要⽤的是在⼤括号⾥的名字, 因为它们就是就是常量符号,它们的类型是int,值则依次从0 到n。如:

enum colors { red, yellow, green } ;

• 就创建了三个常量,red的值是0,yellow是1,⽽green是2。

• 当需要⼀些可以排列起来的常量值时,定义枚举的意义就是给 了这些常量值名字。

枚举量可以作为值

枚举类型可以跟上enum作为类型

但是实际上是以整数来做内部计算 和外部输⼊输出的

枚举量

声明枚举量的时候可以指定值

• enum COLOR { RED=1, YELLOW, GREEN = 5};

枚举只是int

• 虽然枚举类型可以当作类型使⽤,但是实际上 很(bu)少(hao)⽤

• 如果有意义上排⽐的名字,⽤枚举⽐const int⽅ 便

• 枚举⽐宏(macro)好,因为枚举有int类型

结构

声明结构的形式

结构成员

• 结构和数组有点像

• 数组⽤[]运算符和下标访问其成员

• a[0] = 10;

• 结构⽤.运算符和名字访问其成员

• today.day

• student.firstName

• p1.x

• p1.y

结构运算

复合字⾯量

• today = (struct date) {9,25,2004};

• today = (struct date) {.month=9, .day=25, year=2004};

结构指针

• 和数组不同,结构变量的名字并不是 结构变量的地址,必须使⽤&运算符

• struct date *pDate = &today;

结构与函数

结构作为函数参数

• 整个结构可以作为参数的值传⼊函数

• 这时候是在函数内新建⼀个结构变量,并 复制调⽤者的结构的值

• 也可以返回⼀个结构

• 这与数组完全不同

结构指针作为参数

指向结构的指针

结构指针参数

结构中的结构

结构数组

struct date dates[100];

struct date dates[] = { {4,5,2005},{2,4,2005}};

struct dateAndTime { struct date sdate; struct time stime; };

嵌套的结构

结构中的结构的数组

联合

⾃定义数据类型 (typedef)

C语⾔提供了⼀个叫做 typedef 的功能来声明⼀个已有的数据类型的 新名字。⽐如:  

typedef int Length;

使得 Length 成为 int 类型的别名

这样, Length 这个名字就可以代替int出现在变量定义和参数声明的 地⽅了:

声明新的类型的名字

新的名字是某种类型的别名

改善了程序的可读性

typedef struct { int month; int day; int year; } Date;

联合

union AnElt { int i; char c; } elt1, elt2;

elt1.i = 4;

elt2.c = ’a’;

elt2.i = 0xDEADBEEF;

sizeof(union …) = sizeof(每个成员)的最⼤值

存储

• 所有的成员共享⼀个空间

• 同⼀时间只有⼀个成员是有效的

• union的⼤⼩是其最⼤的成员

初始化

• 对第⼀个成员做初始化

第十二周 程序结构

全局变量

定义在函数外⾯的变量是全局变量

全局变量具有全局的⽣存期和作⽤域

• 它们与任何函数都⽆关

• 在任何函数内部都可以使⽤它们

全局变量初始化

没有做初始化的全局变量会得到0值(有默认初值)

只能⽤编译时刻已知的值来初始化全局变量

它们的初始化发⽣在main函数之前

如果函数内部存在与全局变量同名的变量,则全局变 量被隐藏(就近原则)

静态本地变量

• 在本地变量定义时加上static修饰符就成为静态本地 变量

• 当函数离开的时候,静态本地变量会继续存在并保持 其值

• 静态本地变量的初始化只会在第⼀次进⼊这个函数时 做,以后进⼊函数时会保持上次离开时的值

静态全局变量 静态本地变量

• 静态本地变量实际上是特殊的全局变量

• 它们位于相同的内存区域

• 静态本地变量具有全局的⽣存期,函数内的局部作⽤ 域

static在这⾥的意思是局部作⽤域(本地可访问)

编译预处理指令

• #开头的是编译预处理指令

• 它们不是C语⾔的成分,但是C语⾔程序离不开它们

• #define⽤来定义⼀个宏

#define

• 注意没有结尾的分号,因为不是C的语句

• 名字必须是⼀个单词,值可以是各种东⻄

• 在C语⾔的编译器开始编译之前,编译预处理程序 (cpp)会把程序中的名字换成值

• 完全的⽂本替换

• gcc —save-temps

• 如果⼀个宏的值中有其他的宏的名字,也是会被 替换的

• 如果⼀个宏的值超过⼀⾏,最后⼀⾏之前的⾏末 需要加\

• 宏的值后⾯出现的注释不会被当作宏的值的⼀部 分

没有值的宏

• #define _DEBUG

• 这类宏是⽤于条件编译的,后⾯有其他的编译预处理 指令来检查这个宏是否已经被定义过了

预定义的宏

• __LINE__ • __FILE__ • __DATE__ • __TIME__ • __STDC__

带参数的宏

像函数的宏

• #define cube(x) ((x)*(x)*(x)) • 宏可以带参数

带参数的宏的原则

⼀切都要括号

• 整个值要括号

• 参数出现的每个地⽅都要括号

#define RADTODEG(x) ((x) * 57.29578)

可以带多个参数

• #define MIN(a,b) ((a)>(b)?(b):(a))

也可以组合(嵌套)使⽤其他宏

• 在⼤型程序的代码中使⽤⾮常普遍

• 可以⾮常复杂,如“产⽣”函数

• 在#和##这两个运算符的帮助下 •

存在中⻄⽅⽂化差异 • 部分宏会被inline函数替代

⼤程序

多个.c⽂件

• main()⾥的代码太⻓了适合分成⼏个函数

• ⼀个源代码⽂件太⻓了适合分成⼏个⽂件

• 两个独⽴的源代码⽂件不能编译形成可执⾏的程序

编译单元

• ⼀个.c⽂件是⼀个编译单元

• 编译器每次编译只处理⼀个编译单元

项目

• 在Dev C++中新建⼀个项目,然后把⼏个源代码⽂件 加⼊进去

• 对于项目,Dev C++的编译会把⼀个项目中所有的源 代码⽂件都编译后,链接起来

• 有的IDE有分开的编译和构建两个按钮,前者是对单 个源代码⽂件编译,后者是对整个项目做链接

头⽂件

函数原型

• 如果不给出函数原型,编译器会猜测你所调⽤的函数 的所有参数都是int,返回类型也是int

• 编译器在编译的时候只看当前的⼀个编译单元,它不 会去看同⼀个项⺫中的其他编译单元以找出那个函数 的原型

• 如果你的函数并⾮如此,程序链接的时候不会出错

• 但是执⾏的时候就不对了

• 所以需要在调⽤函数的地⽅给出函数的原型,以告诉 编译器那个函数究竟⻓什么样

头⽂件

把函数原型放到⼀个头⽂件(以.h结尾)中,在需要 调⽤这个函数的源代码⽂件(.c⽂件)中#include这 个头⽂件,就能让编译器在编译的时候知道函数的原 型

#include

• #include是⼀个编译预处理指令,和宏⼀样,在编译 之前就处理了

• 它把那个⽂件的全部⽂本内容原封不动地插⼊到它所 在的地⽅

• 所以也不是⼀定要在.c⽂件的最前⾯#include

“”还是<>

• #include有两种形式来指出要插⼊的⽂件

• “”要求编译器⾸先在当前目录(.c⽂件所在的目录) 寻找这个⽂件,如果没有,到编译器指定的目录去 找

• <>让编译器只在指定的目录去找

• 编译器⾃⼰知道⾃⼰的标准库的头⽂件在哪⾥

• 环境变量和编译器命令⾏参数也可以指定寻找头⽂件 的目录

• 在使⽤和定义这个函数的地⽅都应该#include这个头 ⽂件

• ⼀般的做法就是任何.c都有对应的同名的.h,把所有 对外公开的函数的原型和全局变量的声明都放进去

不对外公开的函数

• 在函数前⾯加上static就使得它成为只能在所在的编 译单元中被使⽤的函数

• 在全局变量前⾯加上static就使得它成为只能在所在 的编译单元中被使⽤的全局变量

静态函数 静态全局变量

声明

变量的声明

• int i;是变量的定义

• extern int i;是变量的声明

声明和定义

• 声明是不产⽣代码的东⻄

• 函数原型 • 变量声明 • 结构声明 • 宏声明 • 枚举声明 • 类型声明 • inline函数

• 定义是产⽣代码的东⻄

只有声明可以被放在头⽂件中 是规则不是法律

否则会造成⼀个项目中多个编译单元⾥有重名的实体

某些编译器允许⼏个编译单元中存在同名的函数, 或者⽤weak修饰符来强调这种存在

重复声明

• 同⼀个编译单元⾥,同名的结构不能被重复声明

• 如果你的头⽂件⾥有结构的声明,很难这个头⽂件不 会在⼀个编译单元⾥被#include多次

• 所以需要“标准头⽂件结构”

标准头⽂件结构

• 运⽤条件编译和宏,保证这个头⽂件在 ⼀个编译单元中只会被#include⼀次

• #pragma once也能起到相同的作⽤, 但是不是所有的编译器都⽀持

前向声明

因为在这个地⽅不需要具体知道Node 是怎样的,所以可以⽤struct Node来 告诉编译器Node是⼀个结构

第十三周 文件

格式化输⼊输出

 printf

• %[flags][width][.prec][hlL]type

scanf

• %[flag]type

%[flags][width][.prec][hlL]type

scanf:%[flag]type

[^.]

• //$GPRMC,004319.00,A,3016.98468,N,12006.39211,E, 0.047,,130909,,,D*79

• scanf("%*[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,],%[^,]", sTime,sAV,sLati,&sNW,sLong,&sEW,sSpeed,sAngle,sDate);

类似正则匹配

printf和scanf的返回值

• 读⼊的项目数

• 输出的字符数

• 在要求严格的程序中,应该判断每次调⽤scanf或 printf的返回值,从⽽了解程序运⾏中是否存在问题

⽂件

⽂件输⼊输出

⽤>和<做重定向

输⼊结束

• getchar读到了EOF

• scanf返回⼩于要求读的数量

FILE

• FILE* fopen(const char * restrict path, const char * restrict mode);

• int fclose(FILE *stream);

• fscanf(FILE*, ...)

• fprintf(FILE*, ...)

打开⽂件的标准代码

FILE* fp = fopen(“file”,“r”);
if ( fp ) {
fscanf(fp,...);
fclose(fp);
} else {
...
}

fopen

⼆进制⽂件

• 其实所有的⽂件最终都是⼆进制的

• ⽂本⽂件⽆⾮是⽤最简单的⽅式可以读写的⽂件 • more、tail • cat • vi

• ⽽⼆进制⽂件是需要专⻔的程序来读写的⽂件

• ⽂本⽂件的输⼊输出是格式化,可能经过转码

⽂本 vs ⼆进制

Unix喜欢⽤⽂本⽂件来做数据存储和程序配置

• 交互式终端的出现使得⼈们喜欢⽤⽂本和计算机 “talk”

• Unix的shell提供了⼀些读写⽂本的⼩程序

Windows喜欢⽤⼆进制⽂件

• DOS是草根⽂化,并不继承和熟悉Unix⽂化

• PC刚开始的时候能⼒有限,DOS的能⼒更有限,⼆ 进制更接近底层

• ⽂本的优势是⽅便⼈类读写,⽽且跨平台 • ⽂本的缺点是程序输⼊输出要经过格式化,开销⼤ • ⼆进制的缺点是⼈类读写困难,⽽且不跨平台 • int的⼤⼩不⼀致,⼤⼩端的问题... • ⼆进制的优点是程序读写快

程序为什么要⽂件

配置

• Unix⽤⽂本,Windows⽤注册表

数据

• 稍微有点量的数据都放数据库了

媒体

• 这个只能是⼆进制的

现实是,程序通过第三⽅库来读写⽂件,很少直接读 写⼆进制⽂件了

⼆进制读写

• size_t fread(void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);

• size_t fwrite(const void *restrict ptr, size_t size, size_t nitems, FILE *restrict stream);

• 注意FILE指针是最后⼀个参数

• 返回的是成功读写的字节数

在⽂件中定位

long ftell(FILE *stream);

int fseek(FILE *stream, long offset, int whence);

• SEEK_SET:从头开始 • SEEK_CUR:从当前位置开始 • SEEK_END:从尾开始(倒过来)

可移植性

• 这样的⼆进制⽂件不具有可移植性

• 在int为32位的机器上写成的数据⽂件⽆法直接在int 为64位的机器上正确读出

• 解决⽅案之⼀是放弃使⽤int,⽽是typedef具有明确 ⼤⼩的类型

• 更好的⽅案是⽤⽂本

其它

位运算

按位运算

C有这些按位运算的运算符:

•& 按位的与

•| 按位的或

•~ 按位取反

•^ 按位的异或

•>> 右移

•<< 左移

逻辑运算vs按位运算

• 对于逻辑运算,它只看到两个值:0和1

• 可以认为逻辑运算相当于把所有⾮0值都变成1,然后 做按位运算

位段

把⼀个int的若干位组合成⼀个结构

• 可以直接⽤位段的成员名称来访问

• ⽐移位、与、或还⽅便

• 编译器会安排其中的位的排列,不具有可移植性

• 当所需的位超过⼀个int时会采⽤多个int

 


亲爱的读者:有时间可以点赞评论一下

点赞(0) 打赏

全部评论

还没有评论!
广告位-帮帮忙点下广告