C 语言课程总结
专题 1:字符串部分
scanf 不能输入空格、回车或制表符号(Tab)等空字符,不能被 scanf 读入,输入遇到这些字符时,系统认为字符串输入结束;gets 可以输入此类空字符。
字符输入输出:
1 2 | getchar(a); putchar(a); |
字符串输入输出:
方法一:使用 getchar/putchar 挨个输入/出
1 2 3 4 5 | char str[10]; for (i=0; str[i]!='\0'; i++) { putchar(str[i]); } putchar('\0'); |
方法二:使用 gets/puts 函数:
1 | gets(name); |
方法三:使用 fgets/fputs 函数:
1 | fgets(name,sizeof(name),stdin); |
fgets 与 gets 相比,具有的优点:能够限制输入字符串的长度。
字符串处理函数:
1 | #include <string.h> |
- strlen(char* str); 计算字符串的长度(不包括\0的实际字符的个数),用途:可以用长度控制字符串的输出。
- strcpy(char* dst, char* src); 字符串拷贝,方向:源字符串–>目的字符串。
- strcat(char* dst, char* src); 字符串合并,把源字符串连接在目的字符串后面,目的字符串的长度必须足够长。
- strcmp(char* str1, char* str2); 字符串比较,当出现第一对不相等的字符时,就由这两个字符决定所在的字符串的大小,返回其ASCII码比较的结果。
- strcmp(str1, str2); 若 < 0 为真 <=> st1 < str2。
n 族字符串处理函数:
- strncpy(str1, str2, n); 将 str2 的至多前n个字符拷贝到字符数组 str1 中
- strncat(str1, str2, n); 将 str2 的至多前n个字符连接到字符串 str1 末端,str1 的结束符被 str2 的第一个字符替代。
- strncmp(str1, str2, n); 最多比较 str1 与 str2 的前 n 个字符。
专题 2:指针(Pointer)部分
指针为函数提供修改变量值的手段。
1 | printf("a is %d, &a is %p\n", a, &a); |
其中,%p 表示输出变量 a 的地址值。
变量地址存入指针变量,指针变量指向的数据类型为基类型,用指针变量指向的数据类型作为指针变量的基类型。
1 | int *pa=&a; <====> int *pa; pa=&a; |
一个指针变量只能指向与其类型相同的变量,指针变量只存放地址!
指针变量使用之前必须初始化(赋值),如果指针指向一个非你控制和的内存空间并对该空间进行访问,将可能造成危险!
1 | int *pa= NULL;//定义指针变量并用NULL对其初始化 |
使用指针变量,通过间接寻址输出变量的值:
1 2 | int a=1, *pa=&a; printf("a is %d, &a is %p, pa is %p, *pa is %d", a, &a , pa, *pa); |
输出信息:a is 0, &a is 0023FF74, pa is 0023FF74, *pa is 1
*pa = 9; 修改指针变量 pa 所指向的变量的值。引用指针所指向的变量的值称为指针的解应用(Pointer Dereference),可以像普通变量一样使用 *pa 。
当一个字符串常量出现于一个表达式中时,表达式所使用的值就是这些字符所储存的地址,而不是这些字符本身。因此,你可以把字符串常量赋值给一个“指向字符的指针”,后者指向字符所储存的地址,如:
1 | char *str = "Hello, world"; |
但是,你不能把字符串常量赋值给一个字符数组,因为字符串常量的直接值是一个指针,而不是这些字符本身。简单的说,字符串常量的值是这些字符在内存中的地址。
指针的数值运算:指针的加减运算是以其基类型的字节长度为单位的,指针运算不能乱算,通常由下面两种情况:指针和整数的加减运算;同类型指针之间的减法运算。其它运算,比如乘除、浮点运算、指针之间的加法等,并无意义,所以也不支持。
指针的关系运算:指向同一种数据类型的两个指针才能进行关系运算,值为1或0,不能与非指针类型变量进行比较
1 2 | (*p)++ <===> *p; *p =*p + 1; *p++ <===> *p; p = p + 1;//语法正确,但是无意义! |
普通变量作函数参数-按值调用:形参(parameter) <– 实参变量(variable)
指针作函数参数-按地址调用:指针形参(pointer parameter) <– &(variable)
普通变量作函数参数,形参值的改变不会影响对应的实参;指针变量作函数参数,可以修改实参的值!
1 2 3 4 5 6 7 8 9 10 11 | int main() { int arg = 1; printf("%d", arg);//arg=1 Fun(&arg); printf("%d", arg);//arg=2 } void Fun(int *par) { printf("%d", *par);//par=1 *par = 2; } |
专题 3:结构体
结构体类型的声明:
1 2 3 4 5 6 7 | struct student { long studentID; char studentName[10]; char studentSex; int yearOfBirth; int sorce[4]; };//一定不要忘记结尾的分号 |
以上为一个类型声明的模板,用于生成结构体变量,但并未声明结构体变量,因而编译器不为其分配内存。
结构体变量的定义:
1) 先定义结构体类型再定义变量名
1 | struct student stu1; |
2) 在定义类型的同时定义变量
1 2 3 | struct student { ...... }stu1; |
3)直接定义结构体变量(不指定结构体标签)
1 2 3 | struct { ...... } stu1; |
用typedef定义数据类型名:
1 2 3 4 | typedef struct student { ...... } STUDENT; STUDENT stu1, stu2; // <==> struct student stu1, stu2; |
结构体变量的初始化:
1 2 3 4 5 6 7 8 9 | STUDENT stu1 = {1093320116, "joe", 'M', 1989, {70,80,90,100}}; stu1.studentID = 1093320116 strcpy(stu1.studentName, "joe"); stu1.studentSex = ‘M’; stu1.yearOfBirth = 1989; stu1.score[0] = 70; stu1.score[1] = 80; stu1.score[3] = 90; stu1.score[4] = 100; |
结构体的嵌套:
1 2 3 4 5 6 7 8 9 10 11 12 13 | stydef struct date { int year; int month; int day; } DATE; stydef struct student { long studentID; char studentName[10]; char studentSex; DATAE birthday; int score[4]; } STUDENT; |
访问结构体变量的成员必须使用成员选择运算符(也称原点运算符),当出现结构体嵌套时,必须以连级方式访问结构体成员:
1 2 3 | stu1.birthday.year = 1989; stu1.birthday.month = 6; stu1.birthday.day = 4; |
结构体变量的赋值操作和引用方法:
1 2 | stu2 = stu1;//按结构体的成员顺序逐一对相应成员进行赋值 printf("stu2:%10ld%8s%3c%6d/%02d%02d%4d%4d%4d%4d\n", stu2.studentID, stu2.studentName, stu2.studentSex, stu2.birthday.year, stu2.birthday.month, stu2.birthday.day, stu2.score[0], stu2.score[1], stu.score[3]. stu2.score[3]); |
格式符 %02d 中 2d 前面的前导符 0 表示输出数据时,若左边有多余位,则补 0。
输入结构体变量 stu1 的内容:
1 2 3 4 5 6 7 8 9 10 | scanf("%ld", &su1.studentID); scanf("%s", su1.studentName); //scanf 在用%s时,是指输入一个字符串,不用&,因为字符数组的名字本身就代表这个数组的首地址 scanf(" %c", &su1.studentSex);//%c前面加空格,吸收上次输入结束符 scanf("%d", &birthday.year); scanf("%d", &birthday.month); scanf("%d", &birthday.day); for(i = 0; i < 4; i++) { scanf("%d", &stu1.score[i]); } |
结构体数组的定义和初始化:
1 2 3 4 5 6 7 8 | STUDENT stu[3] = {{1093320104, "王志新", 'M', (1989,6,18), (81,85,76,92,88)}, {......}, {......}, {......}} 结构体指针的定义和初始化: STUDENT stu1; STUDENT *pt = &stu1; //<=====> STUDENT *pt; pt = &stu1; 通过指针访问结构体成员: stu1.studentID = 1; (*pt).studentID = 1; pt->studentID = 1; |
结构体数组的指针:
1 2 | STUDENT stu[10]; STUDENT *pt = stu;//<==========>STUDENT *pt = &stu[0]; |
访问结构体数组指针指向的结构体成员:
1 2 3 4 | STUDENT stu[10]; STUDENT *pt = stu;//pt指向stu[0] pt++;//使pt指向stu[1] pt->sutdentID <=========> stu[1].studentID |
一般情况下我们只用只指向结构体的指针,而不用指向结构体成员的指针
结构体变量作函数参数,形参的改变不影响对应的实参;结构体指针作函数参数,形参的改变会改变相应的实参。指针作函数形参,实参必须为地址值!
结构体变量作函数返回值:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 | struct date{ int year; int month; int day; }; struct data func(struct date p){ p.year = 2012; p.month = 10; p.date = 28; }; int main() { struct date d; d.year = 2000; d.month = 4; d.day = 20; printf("Before function call: %d/%02d/%02d\n", d.year, d.month, d.day);//2000/04/20 d = func(d); printf("After function call: %d/%02d/%02d\n", d.year, d.month, d.day);//2012/10/28 return 0; } |
向函数传递结构体:
- 1) 向函数传递结构体的完整地址:复制整个结构体成员的内容,多个值;函数内对结构内容的修改不影响原结构;内容传递更直观,但开销大
- 2) 向函数传递结构体的首地址:用结构体数组/结构体指针作函数参数;仅复制该结构体的首地址,一个值;修改结构体指针所指向的结构体的内容;指针传递效率高
用户自定义的数据类型:
结构体(struct):把关系紧密且逻辑相关的多种不同类型的变量,组织到统一的名字下,占用相邻的一段内存单元
共用体/联合体(union):把情形互斥但逻辑相关的多种不同类型的变量,组织到统一的名字之下,占用同一段内存单元,每一时刻只有一个数据起作用
1 2 3 4 5 6 | union number{ int i; char c; long l; float f; }; |
sizeof(union number) 取决于占空间最多的那个成员变量;同一内存单元在每一瞬间只能存放其中一种类型的成员;起作用的成员是最后一次存放的成员,不能作为函数参数;不能进行比较操作,只能对第一个成员初始化。
枚举(Enumeration)数据类型:
描述的是一组整型值的集合;用于当某些量仅由有限个数据值组成时
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 | enum weeks { SUN, Mon, TUE, WED, THU, FRI, SAT }; enum weeks today; enum response { no, yes, none }; enum response answer; today = TUE;//其值为2 answer = yes;//其值为1 enum response { no = -1, yes = 1, none = 0 }; |
动态数据结构——单向链表:
链表(Linked Table):线性表的链式存储结构。
特点:用一组任意的存储单元存储线性表的数据,存储单元可以是连续的,也可以是不连续的。为表示每个元素与后续元素的逻辑关系,除存储元素本身信息外,还要存储其直接后继信息。
1 2 3 4 | struct Link{ int data;//数据域:存储数据元素信息 struct Link *next;//指针域:存储直接后继的节点信息 }; |
数据域和指针域两部分信息组成一个节点。结构体声明时不能包含本结构体类型成员,系统将无法为这样的结构体分配内存;但是可包含指向本结构体类型的指针变量:
head–>data|next–>data|next–>data|next–>……->data|NULL。n个节点链接成一个链表(因为只包含一个指针域,故又称线性链表或单向链表)。
动态内存分配函数:
- void* malloc(unsigned int size); 向系统申请大小为 size 的内存快,把首地址返回,若申请不成功则返回 NULL
- void* calloc(unsigned int num, unsigned int size); 向系统申请 num 个 size 大小的内存块,把首地址返回,若申请不成功则返回 NULL。
- void* free(void* p); 释放由 malloc() 和 calloc() 申请的内存块,p 是指向此内存的指针,free 时系统标记此内存为未占用,可被重新分配。
为链表中的节点动态分配内存空间:
1 2 3 4 5 6 | p = (struct Link *)malloc(sizeof(struct Link));//p-->data|next struct link { int data; struct link *next; }; |
一.链表的建立:若原链表为非空,则将新建节点p添加到表尾。
(1) pr->next = p
(2) pr = p
(3) pr->next = NULL
二.链表的删除:若原链表为空表,则退出程序;若待删除节点p是头节点,则将 head 指向当前节点的下一个节点即可删除当前节点。
(1) head = p->next
(2) free(p)
若待删除的不是头节点,则将前一节点的指针指向当前节点的下一节点,即可删除当前节点:
(1) pr->next = p->next
(2) free(p)
若已搜索到表尾(p->next == NULL)仍未找到待删除节点,则显示”未找到”。
三.链表的插入操作:若原链表为空表,则将新节点 p 作为头节点,让 head 指向新节点p:
(1) head = p
p = (struct Link *)malloc(sizeof(struct Link));
p->next = NULL;
p->data = nodeData;
若原链表为非空,则按照节点值(假设已按升序排列)的大小确定插入新节点的位置;若在头节点前插入新节点,则将新节点的指针域指向原链表的头节点,且让 head 指向新节点:
(1) p->next = head
(2) head = p
若在链表中间插入新节点,则将新节点的指针指向下一节点且让前一节点的指针域指向新节点:
(1) p->next = pr->head
(1) p->next = p
若在表尾插入新节点,则末节点指针域指向新节点
(1) pr->net = p
四.链表的输出:
遍历链表的所有节点:
1 2 3 4 5 6 7 | struct link *p = head; int j = 1; while (p != NULL) { printf("%5d%10d\n", j, p->data); p = p->next; j++; } |
专题:文件操作
I/O设备:
输入设备:键盘、鼠标;软盘、硬盘、光驱(以文件的形式);扫描仪、视频采集卡、电视卡、游戏杆、话筒等。
输出设备:显示器、打印机;软盘、硬盘、CD/DVD-RW(以文件形式);音箱……
PS:单纯的输入设备或者输入设备越来越少。
标准输入/输出:
字符界面的操作系统(DOS、Linux、UNIX……)一般都提供标准输入与输出设备;一般情况,标准输入就是键盘,标准输出,就是终端显示器;操作系统有能力重定向标准输入与输出,比如让文件作为标准输入(标准输出);这种重定向程序本身是感觉不到的。
计算机中流(Stream)的概念:一般称为数据流,也叫字节流,比特流。文件流(File stream)等数据流可以倒流,网络上的数据流等数据流不会倒流。流的载体有磁盘文件、终端显示器或打字机、存储器等。
外存:内容容易健忘,所以数据必须保存在“不健忘”的外存上:磁盘(Magnetic disks)、光盘(CD、DVD)、U盘(Flash Memory)……容量大、断电后数据不丢失,可重复使用、永久保存。一般都以文件的形式给用户及应用程序使用。
文件(File):一般指存储在外部介质上有名字的一组相关数据的集合;用文件可长期保存数据,实现数据共享;在 C 语言中,文件可泛指磁盘文件、终端显示器或打印机等。
程序中的文件:在程序运行时由程序在磁盘上建立的一个文件,通过写操作将数据存入该文件;由程序打开磁盘上的某个已有文件,并通过该操作将文件中的数据读入内存供程序使用。
文件与流:程序通过文件打开操作把流与设备联系起来,文件打开后,可在程序和文件之间交换数据;程序通过文件关闭操作断开与文件的联系;所有流的性质都一样(因为流与设备无关,所以能写入磁盘的同一函数也能写入另一设备,如控制台终端等);文件的能力则不同(如磁盘文件支持随机存储,而键盘则不能)。
文件的类型:
二进制文件:一种字节序列,没有字符交换;按照数据在内存中的存储形式(二进制)存储到文件。如整数 123,在内存占 2 个字节,在文件中也占 2 个字节:00000000|01111011
文本文件/ ASCII 码文件:一种字符序列,文件中存储每个字符的 ASCII 码。如整数 123 在文件中占 3 个字节,分别存放这 3 个字符的 ASCII 码:
字符: ‘1’ ‘2’ ‘3’
十进制的ASCII值: 49 50 51
二进制的ASCII值: 0011001 00110010 00110011
文件的格式:数据必须按照存入的类型读出,才能恢复其本来面貌;公开的标准格式:如 bmp、tif、gif、jpg 和 mp3 等类型的文件,有大量的软件能生成和使用这些类型的文件;不公开或加密的文件格式:如 MiscroSoft Word 的 doc 格式等。
缓冲型和非缓冲型文件系统:
缓冲型文件系统:指系统自动在内存中为每一个正在使用的文件开辟一个缓冲区,在读写文件时,数据先送到缓冲区,再传给C程序或外存上;缓冲文件利用文件指针标示文件;缓冲型文件系统中文件操作,也称高级文件操作;高级文件操作函数是 ANSI C 定义的文件操作函数,具有跨平台和可移植的能力
非缓冲型文件系统:不会自动设置文件缓冲区,缓冲区需由程序员自己设定;非缓冲文件系统没有文件指针,它使用称为文件号的整数来标识文件。
高级文件操作函数:
以下介绍的函数均定义在<stdio.h>中:
文件的打开:
FILE *fopen(const char *filename, const char *mode);
FILE *fp = fopen(“C:\CONFIG.SYS”, “rw”);
filename 是文件名,包含路径。如果不包含路径,表示打开当前目录下的文件
mode 是打开方式,常用为“r”、“w”、“rw”、“a”,分别表示只读、只写、读写和添加(追加),“rb”表示只读二进制文件。详细用法如下:
- “r”:只读,必须是已存在的文件
- “w”:只写,不论该文件是否存在,都新建一个文件
- “a”:追加,向该文件尾追加数据,该文件必须存在
- “r+”:读写,打开一个已经存在的文件,用于读写
- “w+”:读写,建立一个新文件,可读可写
- “a+”:读写,向文件尾追加数据,也可读
对应的二进制文件打开方式分别为:”rb”、”wb”、”ab”、”rb+”、”wb+”、”ab+”;返回值为指向此文件的指针,留待以后使用;如果打开失败,返回值为NULL。
文件指针(File Pointer):
FILE *p; FILE 型的指针变量,标识一个特定的磁盘文件,与文件相关联的每一个流都有一个 FILE 类型的控制结构,定义有关文件操作的信息,用户绝对不应修改。
1 2 3 4 5 6 7 8 9 10 11 | typedef struct { short level; /*缓冲区‘满’或‘空’的成都*/ unsigned flags; /*文件状态标志*/ char fd; /*文件描述符*/ unsigned char hold; /*如无缓冲区不读字符*/ short bsize; /*缓冲区的大小*/ unsigned char *buffer; /*数据缓冲区的位置*/ unsigned char *curp; /*指针当前的指向*/ unsigned istemp; /*临时文件指示器*/ short token; /*用于有效性检查*/ } FILE; /*在stdio.h文件中定义*/ |
文件的关闭:
int fclose(FILE *fp); 把遗留在缓冲区中的数据写入文件,实际操作系统级的关闭操作;同时,释放与流联系的文件控制块,以后可以重复使用这部分空间;多数情况下,系统限制同时处于打开状态的文件总数,因此,打开文件前先关闭无用文件是必要的
fclose 函数的返回值:当顺利地执行了关闭操作,返回值为 0;如果返回值为非零值,表示关闭时有错误;一般只有驱动器中无盘或盘空间不够时才失败,关闭失败会引起数据丢失、文件的破坏和程序中的随机错误。
字符读写:
int gfetc(FILE *fp); 从 fp 读出一个字符,将位置指针指向下一个字符;若读取成功,则返回该字符,若读到文件尾,则返回 EOF(EOF 是一个符号常量,在 stdio.h 中定义为 -1)。
int fputc(int c, FILE *fp); 向 fp 输出字符 c;若写入错误,则返回 EOF,否则返回 c。实例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | #include <stdio.h> #include <stdlib.h> int main() { FILE *fp; char ch; if ((fp = fopen("demo.txt","w")) == NULL) { printf("Failure to open demo.txt!\n"); wait(0); } ch = getchar(); while (ch != '\n') { fputc(ch, fp); ch = getchar(); } fcolse(fp); return 0; } |
函数 feof() 检查是否到达文件尾,当文件位置指向文件尾时,返回非 0 值,否则返回 0 值;
1 2 3 4 5 6 7 8 9 | while((ch = fgetc(fp)) != EOF) { putchar(ch); } ch = fgetc(fp); while (!feof(fp)) { putchar(ch); ch = fgetc(fp); } |
字符串读写:
char *fgets(char *s, int n, FILE *fp); 从 fp 指向的文件中读取字符串并在字符串末尾添加 ‘\0’,然后存入 s,最多读 n-1 个字符;当读到回车换行符、文件末尾或读满 n-1 个字符时,函数返回该字符串的首地址。
特例:fgets(buf, sizeof(buf), stdin);
int fputs(const char *s, FILE *fp); 将字符写入文件中;若出现写入错误,则返回EOF,否则返回一个非负数。