跳转至

Info

C 语言是一种通用的、面向过程式的计算机程序设计语言。1972 年,为了移植与开发 UNIX 操作系统,丹尼斯·里奇在贝尔电话实验室设计开发了 C 语言。
C 语言是一种广泛使用的计算机语言,它与 Java 编程语言一样普及,二者在现代软件程序员之间都得到广泛使用。
当前最新的 C 语言标准为 C18 ,在它之前的 C 语言标准有 C17、C11...C99 等。

C语言复习笔记

1 绪论

Hello, world!

1
2
3
4
5
#include<stdio.h>
int main()
{
  printf("Hello, world!\n");
}

C 程序的组成

宏定义, 定义常量, 定义宏函数
文件引用, 引用系统文件, 引用用户文件
外部变量
函数向前引用声明
主函数 (带命令行参数)
{
  函数向前引用说明
  局部变量
  调用函数
  分配动态内存
  调用宏定义函数
  文件操作
  输入输出
}

2 基本数据类型

常量

  • int long long long
  • float double
  • char
  • void (空类型)

变量名

以字母或下划线开头, 后面可以跟若干个字母、数字和下划线, 区分大小写

整型

  • 十六进制:0x 开头
  • 八进制:0 开头
  • 长整型: 后面加 l
  • 超长整型: 后面加 ll

转义字符

  • \n \t \' \" \\ \0
  • \ddd 1~3 位八进制数所代表的 ASCII 码字符
  • \xhh 1~2 位十六进制数所代表的 ASCII 码字符

计算机存储单位

GB TB PB

3 数据的输入和输出

数据输入输出

输入输出函数中, 键盘是标准输入设备 stdin, 显示器是标准输出设备 stdout

头文件 #include<stdio.h>

基本的格式输出语句

printf("格式控制", 输出列表);

返回值为本次调用输出字符的个数, 包括回车等控制符

格式说明符

  • 十进制: %d %ld
  • 八进制: %o %lo
  • 十六进制: %x %lx

  • 小数: %f(普通) %e(指数) %g(自动选择)

  • 字符:%c
  • 字符串: %s
  • 变量的内存地址:%p
  • %:%%

% 和说明符之间加入整数以限制宽度, 加入小数以限制精度, 加入 - 左对齐, 加入 0 补零而不是空格, 加入 + 显示正号 变场宽输出 printf("%*.*f", m, n, f)

强制转换

(类型名)(表达式)

在数据前加 (int/float 等数据类型)

格式输入函数

scanf("格式控制", 内存地址表);

返回值为本次 scanf() 调用正确输入的数据项的个数

格式控制

整型、字符、字符串与输出控制相同

  • float: %f
  • double: %lf

跳过读入:scanf("%d%*d%d%d", &x, &y, &z);

取地址符

&

字符输出函数

putchar(c);

字符输入函数

getchar(); 头文件 #include <stdio.h>

  • _getch(); 头文件 #include <conio.h>在按下相应键的同时接收从键盘输入的 一个字符。
  • _getche(); 由键盘输入的字符不在屏幕上显示
  • _ungetch(c); 将字符放回输入流

4 表达式与宏定义

复合算术赋值运算符

+= -= *= /= %=

整型相除

7/6==1

(1/2+1/2)==0

关系运算符

< <= > >= == !=

逻辑运算符

&& || !

1<x<2 \(\iff\) 1<x && x<2

书写时需要注意运算符优先级, 不确定带括号; 最好直接带括号

若已经能够判断则逻辑运算符后面的语句不执行

其他运算符

自增自减运算符 ++i i++ --i i--

sizeof()

逗号运算符: 从左到右计算表达式的值, 最右边表达式的值就是整个逗号表达式的值

常用头文件

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

宏定义

#define 符号常量名 字符串

不过这样的宏定义只是做简单的字符串替换

一行写不下可以在杭为加 \ 在下一行继续写

带参数的宏定义

#define 宏名 (参数表) 字符串

#define P(x) printf ("%d\n", x)

一般需要将参数用括号括起来

在宏定义中使用 # 1. 将变量转换为字符串 如 #define PR(x) printf("%s=%d\n", #x, x) 2. 连接两个字符串 如 #define MP(x) printf("%d",a##x)

5 选择结构

if

1
2
3
if(值为真或假的表达式){语句}
else if(){...}
else {...}

else 只与同层最近的 if 配对

注意小数确定是否相等时用差值小于一个精度而不是直接用 ==

条件运算符

表达式 1 ? 表达式 2 : 表达式 3

switch

1
2
3
4
5
6
7
8
switch(int/char)
{
  case 1: ...; break;
  case 2: ...; break;
  case 3: ...;
  case 4: ...; break;
  default: ...;
}

6 编译预处理

文件包含

#include < 文件名 >

#include "文件名"

一般不用 #include 命令引用 .c 文件 (C++ 模版文件可能要这么做)

条件编译

1
2
3
4
5
#ifdef
  ...
#else
  ...
#endif
1
2
3
4
5
6
7
#if ...
  ...
#elif ...
  ...
#else
  ...
#endif

文件 item.h

1
2
3
4
#ifndef ITEM_H
#define ITEM_H
......
#endif // ITEM_H

#undef 将已经定义的标志服变为未定义

pragma

一般形式 #pragma token-string, 这指导编译器如何进行编译

#pragma once, 让编译器把指定的文件只包含一次, 防止此文件被多次引用出现的重复定义等错误.

#pragma warning(disable:4996) 将 4996 类警报置为失效,让编译器不再显示这类警告

line

#line 数字 num ["文件名"]

从该行之后, 编译信息的行数将从第 num 行开始计算, 而不是原先代码的行数

7 循环结构

while

  • while(){...}
  • do {...} while ();

for

for(i=0; i<=n; i++) {...} - i=0i++ 可以省略 - i<=n 也可以省略, 但是需要 break; 否则无法跳出循环

continue

结束本次循环的执行,但不退出循环结构

8 模块设计

C 语言中, 函数分以下两种: 标准库函数, 用户自己定义的函数

函数的一般形式

1
2
3
4
5
类型标志符 函数名 (形参列表)
{
  说明部分
  语句部分
}

void 类型的函数不返回函数值, 只是完成某个任务

函数中的返回语句 return 表达式;

C 程序其中必须有且只能有一个主函数 int main()

C 程序总是从主函数开始执行 (不管它在程序中的什么位置), 而其他函数只能被调用

函数的说明

类型标志符 函数名 (形参列表);

这里形参列表可以是 形参 1 类型, 形参 2 类型, ..., 也可以是 形参 1 类型 形参名 1, 形参 2 类型 形参名 2, ....

这种对被调用函数的说明称为函数原型 / 函数向前引用说明

函数参数传递

C 语言函数传参为值传递

全局变量

可以通过定义全局变量的方法实现各函数之间的参数传递, 全局变量的有效范围是从定义变量的位置开始到 本源文件结束

全局变量的引用说明 entern 类型名 变量名;

extern 全局变量的用途 1. 在同一文件中, 为了使全局变量定义点之前的函数中也能使用该全局变量, 则应在函数中用 extern 加以说明. 2. 使一个文件中的函数能使用另一个文件中的全局变量. 3. 利用静态外部变量, 使全局变量只能被本文件中的函数引用, 控制其作用域

变量的存储类型

  1. 数据类型: 如 int float char double
  2. 存储类型: 分为自动类型 auto(函数中局部变量默认为 auto 类型), 静态类型 static, 寄存器类型 register, 外部类型 extern.

数据的存储类型决定了数据的存储区域

静态变量

static 说明的局部变量或外部变量在函数调用结束后其内存不会消失而保留原值, 即其占用的存储单元不释放, 在下一次调用时仍为上次调用结束时的值.

对局部静态变量赋初值是在编译时进行的

内部函数与外部函数

在函数定义前加 static 使函数成为只能在本文件的其他函数中调用的函数, 这种函数被称为内部函数

在函数定义前加 extern 使函数成为外部函数 (也即一般定义的函数)

递归函数

9 数组

一维数组

定义 类型名 数组名 [常量表达式];

注意 [ ] 中必须是常量

在 C 语言中, 只能逐个引用数组元素, 不能一次引用数组中的全部元素

二维数组

定义 类型名 数组名 [常量表达式 1][常量表达式 2];

字符串

字符串常量以结束符 \0 结尾

以下写法等价

1
2
3
4
5
char a[15]={"how do you do?"};
char a[15]="how do you do?";
char a[ ]="how do you do?";
char a[]={'h', 'o', 'w', '','d','o',' ','y','o','u',' ','d','o','?','\0'};
char a[]={104,111,119,32,100,111,32,121,111,117,32,100,111,63, 0};

常用的字符串处理函数

#include <string.h>

  • puts(s) 输出字符串
  • gets(s) 读入字符串
  • strcat(s1, s2) 将 s2 连接到 s1 后面, 并返回 s1 地址
  • strcpy(s1, s2) 将 s2 复制到 s1 中
  • strncpy(s1, s2) 将 s2 的前 n 个字符复制到 s1 中
  • strcmp(s1, s2) 前大返回大于 0 的数
  • strlen(s) 求字符串长度 (不包括 \0)
  • strstr(s1, s2) 确认 s2 是否在 s1 中出现过, 是则返回第一次出现的位置, 否则返回 NULL
  • strlwr(s) strupr(s)
  • sprintf(字符数组名, "输出格式", 变量列表)
  • sscanf(字符数组名, "输入格式", 变量列表)

数组作为函数参数

int function(int a[], int n){...}

一般需要另外一个参数传递数组长度

二维传递可以强制转为一维数组

二维数组下标 i*n+j

10 指针

int *p = &x;

定义了整型指针 p, 指向整型变量 x 的内存地址

*p = 3;

现在 *p 指的是指针 p 所指的值, 此语句使得变量 x 的值被更改为 3

1
2
3
4
5
6
7
char *p, *q;// 定义了两个字符指针 p 和 q, 其分别可以指向一个字符
int *a[10];// 定义了含有十个整型指针的指针数组, 数组中的每一个元素是一个指针, 可以指向一个整数
float **r;// 定义了指向浮点型指针的指针
double (*s)[100];// 定义了一个指针 s, 这个指针指向一个 double 数组, 这个 double 数组有 100 个元素
int *f();// 返回值为整型指针的函数
void (*g)();// 定义了一个函数指针 g, 它可以用来指向一个 void 类的函数
double (*h[10])();// 定义了 void 类型的函数指针数组 h[10], 如 h[1] 可以用来指向一个返回值为 double 的函数
  • *r++(*r)++:
  • *r++: 指针加, 指向下一个单元
  • (*r)++: 指针所指的内容加

指针作为函数参数可以实现地址传递

10 动态内存申请与释放

malloc

malloc 函数的原型 void *malloc(申请内存的字节数)

使用, 如

  • p=(char *)malloc(sizeof(char)*20);
  • q=(double **)malloc(sizeof(double *)*10);
  • a=(int (*)[4])malloc(sizeof(int)*4*5);

一般用 malloc() 申请内存需要判断是否申请成功

1
2
3
4
5
if (p == NULL)
{
    printf("Can’t get memory!\n");
    exit(1); /* 强制终止当前程序的执行 */ 
}

使用 malloc() 可以先读入数组长度然后定义一个数组

free

函数原型 void free(void *ptr);

malloc() 申请的动态内存块, 需要用 free() 按照与申请顺序时向量的顺序释放回内存堆, 防止内存泄漏

字符串与指针

char s[] = "...";

char *s = "...";

二者的联系与区别

  1. 字符数组由元素组成; 字符指针变量中存放的是地址
  2. 字符数组中的元素可以单个字符赋值; 字符指针变量赋值是字符串首地址
  3. (字符) 数组名是常量, 值无法改变; 字符指针变量的值可以改变
  4. 可以用加号或下标形式索引字符串的单个元素. 如 char *s = "..."; printf("%c", s[4]); printf("%c", *(s+4)); 如 "..."+4 "..."[4]
  5. 指针指向的字符串值无法修改
  6. 可以通过输入字符串的方式为字符数组输入字符元素; 但不能通过输入函数让字符指针变量指向 一个字符串, 因为由键盘输入的字符串, 系统不分配存储空间
  7. 可以用指针变量所指向的字符串表示程序中的任何字符串, 用字符数组也可以
  8. 字符型指针数组可构造紧凑型字符串数组, 例如: char *str[]={"abc", "de", "fghij", "k"}; 也可以用字符型二维数组来存放字符串数组, 例如: char str[][6]={"abc", "de", "fghij", "k"};
  9. 以用 sizeof 操作符求字符串所占内存的大小

函数指针

指向函数的指针指向函数的入口地址

函数指针的定义:类型标识符 (* 指针变量名)(参数类型);

给函数指针变量赋值时, 只需给出函数名

调用方式:(* 函数指针变量名)(实参表) 函数指针变量名 (实参表)

返回指针值的函数

类型标识符 * 函数名 (形参表){...}

main 函数的形参

int main(int argc, char *argv[])
{...}

其中: argc 的值是命令行参数个数 +1, argv 是字符型指针数组, 指向每一个参数字符串

字符串转 int 和 float

#include <stdlib.h>

atoi() atof()

11 结构体与联合体

struct 结构体

定义: struct 结构体类型名 {成员表};

注: 分号别掉了

用大括号和逗号进行赋值

. 取结构体中变量的值

在定义结构体类型变量时, 需要使用结构体类型的全称. 如 struct date birthday;

也可以同时定义结构体类型和变量:

1
2
3
4
5
6
struct date
{
  int year;
  int month;
  int day;
}birthday1, birthday2;
  • 也可以定义无名结构体
  • 结构体可以嵌套
  • 结构体变量可以作为函数参数
  • 结构体变量也可以定义数组

当结构体类型的指针变量 p 指向一个同类型的结构体类型变量 a 后, 下列四种表示是等价的:

a. 成员 (*p). 成员 p-> 成员 p[0]. 成员

pack

在程序开头加上 #pragma pack(4)

则其中结构体采用 4 字节对齐

也可以通过调整结构体内部变量顺序的方式来节省空间

综合应用 链表

联合体

一般形式

union 联合体名
{成员表};

联合体 unionstruct 的定义和使用是相同的. 但 union 中的各个数据之间共享同一个单元的, 所占内存单元的大小是几个数据项中长度最大的一个

枚举类型

enum 枚举类型名 {枚举元素列表};

enum WEEK {SUN, MON, TUE, WED, THU, FRI, SAT};, 第一个值从 0 开始自动赋值. 也可以在常量名后面加等号自己赋值

自定义类型名

typedef 原类型名 新类型名;

typedef 也可以用来声明数组类型, 如 typedef int NUM[100];

12 文件

文本文件和二进制文件

用一般的编辑器能编辑、人能直接读懂的文件是文本文件, 是由 ASCII 字节流组成的 (也可能是其他编码)

.txt .c .h. 除了这些文件基本上都是二进制文件

定义文件指针

FILE * 指针变量名;

打开文件

fp = fopen("文件", "文件打开方式");

打开方式

  • r 只读 为读打开一个文件. 若指定的文件不存在, 则返回空指针值 NULL.
  • w 只写 为写打开一个新文件. 若指定的文件已存在, 则其中原有内容被删去; 否则创建一个新文件.
  • a 追加写 向文件尾增加数据. 若指定的文件不存在, 则创建一个新文件.
  • r+ 读写 为读写打开一个文件若指定的文件不存在, 则返回空指针值 NULL.
  • w+ 读写 为读写打开一个新文件. 若指定的文件已存在, 则其中原有内容被删去; 否则创建一个新文件.
  • a+ 读与追加写 为读写向文件尾增加数据打开一个文件, 若指定的文件不存在, 则创建一个新文件.

在后面附加 b 表示打开二进制文件

打开文件时的判断

1
2
3
4
5
6
FILE *fp;
if ((fp=fopen("文件", "文件打开方式")) == NULL)
{
    printf("Cannot open this file !\n");
    exit(0); /* 终止调用过程 */
}

关闭文件

fclose(fp);

文本文件读写

  • fgetc(fp); 读字符
  • fputc(c, fp); 写字符
  • fgets(char *s, int n, FILE *fp);fp 中读 n 个字符作为一个字符串放到 s
  • fputs(const char *s, FILE *fp); 写字符串

数据块读写

feof(fp) 遇到文件尾返回非 0, 否则返回 0

  • fread(buffer, size, count, fp);fp 中读 count 个字节数为 size 的数据放到 buffer
  • fwrite(buffer, size, count, fp);buffer 中写 count 个字节数为 size 的文件到 fp

格式读写

  • fscanf(文件指针, 格式控制, 地址表);
  • fprintf(文件指针, 格式控制, 输出表);

可以替换文件指针的量: 标准输入的设备名 stdin, 标准输出的设备名 stdout

文件定位

rewind(fp);(倒带) 将读写指针移动到文件开头

fseek(FILE *fp, long offset, int origin);

  • offset 为 ** 偏移量 **
  • origin 为 ** 起始位置 **:

  • SEEK_SET0

  • SEEK_CUR1
  • SEEK_END2

long ftell(FILE *fp); 返回文件当前读写指针的位置

其他函数

  • fflush(fp); 清空文件的输入输出缓冲区
  • clearerr(fp); 清除由于读写等操作失败引起文件输入输出缓冲区处于的错误状态
  • clearerr(stdin/fp); 应该和 fflush(stdin/fp); 配对使用

13 位运算

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

运算优先级

!(逻辑非)按位取反 ~算术运算符左移运算符 << 右移运算符 >>关系运算符按位与 & 按位异或 ^ 按位或 |逻辑与 &&逻辑或 ||赋值运算符

位段

struct 位段结构类型名 {成员表};

1
2
3
4
5
6
struct packed_d
{ 
  unsigned short f1:2;
  unsigned short f2:1;
  //unsigned char :2;
};

定义了一个位段结构类型,名为packed_d,共包含2个成员(又称为位段),每个成员均为无符号short类型,其中成员f1占2个二进制位,f2占1个二进制位

注意:

  1. 位段成员的类型必须是unsigned型;
  2. 在位段结构类型中,可以定义无名位段,这种无名位段具有位段之间的分隔(或占位)作用.如果无名位段的宽度值为0,则表示下一个位段从一个新的字节开始存放
  3. 每个位段(成员)所占的二进制位数一般不能超过 编译器的一个字长(比如32位)
  4. 位段不能说明为数组,也不能用指针指向位段成员
  5. 不能用 sizeof() 求段位成员的大小
  6. 在位段结构类型定义中, 可以包含非位段成员