C语言程序设计·

一、整体要求·

  1. C语言的特点以及C语言程序的组成:
  2. C语言主要的数据类型,包括整型、实型、字符型等常量与变量和变量的赋值;理解原码、反码和补码;用typedef定义类型;
  3. C语言各种类型数据之间的混合运算;
  4. C语言算术表达式、关系表达式和逻辑表达式,表达式sizeof的含义。

二、知识要点·

1. C 程序的基本结构·

(1)C语言的特点 (2)C程序的基本组成

  • 函数是c程序的基本组成单元,由函数头和函数体两部分组成
  • 每个 C 程序都至少有一个主函数main()
  • 主函数参数:argc 用来统计命令行参数个数,argv 用来存放参数字符串;
  • 预处理指令:是在真正编译开始之前由编译器调用的独立程序指令;
  • 头文件:调用库功能;加强类型安全检查;<> 适用于工程或标准头文件;"" 适用于自定义头文件;
  • 宏定义:用于定义一个标识符常量或带参的宏,本质上是一种文本替换;

2. C语言常量、变量和表达式·

(1) 常量: 数字常量、字符常量和字符串字面量

​ 小写字母比大写字母的ASCII码值大32

(2) 变量: 变量名和变量类型,变量的赋值和类型转换

整数类型

int 可以表示正数、负数和 0,一般是 32 位,在典型实现中表示的范围是 231-2^{31}23112^{31} - 1。而 unsigned int 表示非负整数,范围是 0 到23212^{32} - 1

类型 存储大小 值范围
char 1 字节 -128 到 127 或 0 到 255
unsigned char 1 字节 0 到 255
signed char 1 字节 -128 到 127
int 2 或 4 字节 -32,768 到 32,767 或 -2,147,483,648 到 2,147,483,647
unsigned int 2 或 4 字节 0 到 65,535 或 0 到 4,294,967,295
short 2 字节 -32,768 到 32,767
unsigned short 2 字节 0 到 65,535
long 4 字节 -2,147,483,648 到 2,147,483,647
unsigned long 4 字节 0 到 4,294,967,295

浮点类型

类型 存储大小 值范围 精度
float 4 字节 1.2E-38 到 3.4E+38 6 位有效位
double 8 字节 2.3E-308 到 1.7E+308 15 位有效位
long double 16 字节 3.4E-4932 到 1.1E+4932 19 位有效位

(3) 算术表达式: 算术运算符、增量(自增)和减量(自减)运算符、位运算和复合赋值运算符

​ 常用的位运算符有&(按位与)、|(按位或)、^(按位异或)、~(按位取反)。

(4) 强制类型转换

  • 强制类型转换运算符:(类型名)(表达式) 例如 (int)(x + y) : 将 x+y 的值转换成int型

  • 强制类型转换时,得到所需中间数据,原来变量的数据的类型没有发生变化。

  • 强制类型转换符优先级高于%运算。

  • 优先级:单目运算符 > 算术运算符 > 关系运算符 > 逻辑运算符 条件运算符 > 赋值运算符。

(5)数据输入/输出函数

scanf函数的使用

1
scanf("%d",&n);
输出控制符 数据类型
%d int
%ld long int
%c char
%f float
%lf double
%x(或%X, #x %#X)
2f 2F 0x2f 0X2F
int 或 long int 或 short int
%s 字符串

在scanf中,除了char数组整个输入的情况不加&之外,其他变量类型都需要加&。

三种实用的输出格式

(1) %md

%md可以使不足 m 位的 int 型变量以m位进行右对齐输出,其中高位用空格补齐;如果变量本身超过 m 位,则保持原样。

1
2
3
4
5
6
7
	int a = 123, b = 1234567;
printf("%5d\n",a);
printf("%5d",b);

/*输出: 123
1234567
可以看到,123 有三位数字,不足五位,因此前面自动用两个空格填充,使整个输出凑足五位;而1234567 已经大于五位,因此仍然直接输出。*/

(2) %0md

​ %0md只是在%md中间多加了0。和%md的唯一不通点在于,当变量不足m位时,将在前面补足够数量的0而不是空格。

(3) %m.nf

​ %m.nf 指定数据宽度和小数位数,%7.2f指定 输出的数据占7列,其中包括2位小数,注意小数点占一位。

常用math函数

pow(x,y) 计算x^y的值
sqrt(x) 计算x的平方根
round(double x) 将 x 四舍五入
floor(x)和 ceil(x) 计算x的向下取整和向上取整,返回类型为 double 型。
fabs(x) 计算 x(高精度的double,float)的绝对值
abs(x) 计算 x(整数)的绝对值

(6)常量的符号表示方法:常量宏、枚举常量

#define:宏定义,C的一种编译预处理指令。是纯粹的文字替换,不进行类型检查。结尾不加 ;

1
#define max(a,b) a>b?a:b

const :定义的常数是变量,也带类型,存在于程序的数据段,并在堆栈中分配了空间。如果一个变量被const修饰,那么它的值就不能再被改变。

3. C语言条件语句和开关语句·

(1) 关系运算符和逻辑运算符

关系运算符:>、<、==、>=、<=、!=

逻辑运算符:!、&&、||

(2)常考运算符优先级·

  • 高优先级:()、->、前自增自减;!、寻址、取址、后自增自减;
  • 中优先级:乘除;加减;左移右移;比较运算符;
  • 低优先级:与;异或;或;逻辑和;逻辑或;
  • 最低优先级:逗号。

(3) 逻辑表达式

  • 表示逻辑运算结果时,以数值1代表“真” , 以 0 代表“假” 。
  • a && b && c 只有a为真(非0),才需要判断b的值,只有当a和b都为真的情况下才需要判别c的值。如果a为假就不必判断b和c(此时整个表达式已经确定为假),a为真,b为假,就不用判别c。
  • a || b || c 只要a为真(非0),就不必判断b和c。 只有a为假 ,才判别b。a 和 b都为假才判别c。

(4)条件语句:条件、复合语句、条件语句的嵌套和级联、条件运算符和条件表达式

  • 三目运算符:表达式1 ?表达式2 :表达式3 ,表达式1成立返回表达式2的值,否则返回表达式3的值 。

(5) switch 语句

  • break 语句的使用: 在每个 case 标签的代码块结束处通常需要使用 break 语句来终止 switch 语句的执行。如果没有 break 语句,程序将会继续执行下一个 case 标签中的代码,直到遇到 break 语句或 switch 语句结束。
  • switch 表达式的类型: switch 语句中的表达式必须是整数类型(char、short、int或枚举),或者是能够隐式转换为整数类型的表达式。
  • 默认情况的可选性: switch 语句中的 default 标签是可选的。如果没有匹配的 case 标签,则会执行 default 标签下的代码块(如果存在)。
  • case 标签的唯一性: 在 switch 语句中,每个 case 标签必须是唯一的,不能有重复的值。

4. C 语言循环语句和 goto 语句·

(1) while 语句、for 语句和 do while 语句

  • while是先判断循环条件是否成立,do while是先执行一遍循环体,再进行判断循环条件。

(2)循环语句的选择和使用

(3)逗号表达式

(4)循环语句的嵌套

(5)循环中的非常规控制(break和continue)、goto 语句

  • break语句:结束整个循环,不再判断循环条件是否成立。
  • continue语句:只结束本次循环,直接执行下一次循环。

5. C 语言函数·

(1) 函数的基本概念

(2) 函数的调用、结构和定义

(3) 函数的调用关系和返回值

(4) 局部变量和全局变量

  • 局部变量和全局变量同名,会怎样?

    在局部变量的作用范围内,局部变量有效,全局变量被“屏蔽”,即全局变量不起作用。

  • 只有 全局变量初始化时 的默认值为0,而 局部变量 默认值是不确定的

  • static声明一个变量的作用是:

    1)对局部变量用static声明,把它分配在静态存储区,该变量在整个程序执行期间不释放,其所分配的空间始终存在。 2)对全局变量用static声明,则该变量的作用域只限于本文件模块(即被声明的文件中)。

(5)函数参数的传递

  • 传值调用:该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数
  • 引用调用:通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。

(6)标准库函数 (7)递归函数

6. C语言数组·

(1) 一维数组:定义和初始化、复制、数组参数·

  • 定义数值型数组时未被指定初始化的数组元素,系统自动初始化为0(字符型数组,初始化为’\0’ , 如果是指针型数组,则初始化为NULL,即空指针。)

  • memset通常只用于用于字符数组的初始化如果使用memset初始化int数组,初始值只能设为0或-1

    1
    2
    3
    //void *memset(void *s, int c, unsigned long n);
    char a[5];
    memset(a,'a',sizeof(a));// "aaaaa"
  • 动态计算数组的元素个数

    1
    #define NumberOf(x) 	(sizeof(x) / sizeof(x[0]))
  • 数组存储元素是从所占用的低字节开始存储,而变量的内存寻址是从大到小, 所以存储数据时会从高字节开始存储

  • 数组的地址就是数组首个元素的地址,也是数组名的地址。

  • C语言没有数组越界检查,如果访问超过定义的数组长度,结果不可预期,有可能报错,有可能可以访问但访问到的是脏数据。

  • mencpy复制数组

    1
    2
    3
    4
    5
    char *s="http://www.runoob.com";
    char d[20];
    memcpy(d, s+11, 6);// 从第 11 个字符(r)开始复制,连续复制 6 个字符(runoob)
    // 或者 memcpy(d, s+11*sizeof(char), 6*sizeof(char));
    // d runoob

(2) 字符串和字符数组·

  • C语言中没有字符串类型,字符串存放在字符型数组中。
  • 字符串结束标志:'\0',存储字符串时会自动加一个'\0' 作为结束符。因此字符串数组长度:数组长度 = 字符串长度 + 1;

(3) 标准字符串函数·

  • 使用字符串处理函数,要在本文件开头添加:#include<string.h>

  • char *strcat(char *dest, char *src) : 把字符串src添加到dest结尾处(覆盖dest结尾处的’\0’)并添加’\0’

  • char *stpcpy(char *dest, char *src) : 拷贝一个字符串到另一个,并返回dest

  • char *strncpy(char *dest, char *src, int n):把字符串src中前n个字符复制到dest中,并返回dest

  • int strcmp(char *str1, char *str2):

    ​ str1 > str2,返回一个正整数。​ str1 < str2,返回一个负整数。​ str1 = str2,返回函数值0。

    ​ 即:两个字符串自左向右逐个字符相比(按ASCII值大小相比较),直到出现不同的字符或遇’\0’为止。

  • unsigned int strlen(char *s):计算字符串s的长度,不包含’\0’

  • strlwr函数——转换小写字母

  • strupr函数——转换为大写的函数

(4) 二维数组:定义、引用、访问、数组参数·

7. C 语言指针·

(1) 地址与指针

  • & 取地址运算符。&a是变量a的地址。
  • * 指针运算符(或称“间接访问” 运算符 ),*p代表指针变量p指向的对象。

(2) 指针变量: 定义和赋值、访问、参数和返回值

  • 专门用来存放另一变量的地址(即地址)的变量,称为“指针变量”

  • int* p1, p2 ;* 只会结合第一个变量,即只有p1是int * 型的

  • 给指针变量赋值:

    1
    2
    int a;
    int *p=&a;

    注意 p 是变量名,类型是int *,p存放a的地址,通过*p可以访问a的地址获得a的值。

  • 通过指针交换变量值:

    1
    2
    3
    4
    5
    void swap(int* a,int* b) {
    int temp = *a;
    *a = *b;
    *b = temp;
    }

(3) 指针运算:指针与整数的加减、指针相减和比较、强制类型转换和 void*指针、不合法的指针运算、指针类型与数组类型的差异: (4) 指针与数组

  • 数组名可以作为数组首地址使用,即a = &a[0]

  • 数组指针(也称为行指针) 定义为:int( * p)[n]; (注意优先级:()>[]> *)(int (*p)[5]定义了一个指向含有5个元素的一维数组的指针。)当数组指针指向一个一维数组时:

    1
    2
    3
    int( * p)[n];    //定义了指向含有n个元素的一维数组的指针
    int a[n]; //定义数组
    p=a; //将一维数组首地址赋值给数组指针p

    ()优先级高,说明p是指针,指向一个整型的一维数组。这个一维数组的长度是n,也可以说p的步长为n。当p+1时,p指针会跨过n个整型数据的长度。

    当数组指针指向一个二维数组时:

    1
    2
    3
    4
    int(* p)[4];   //定义了指向含有4个元素的一维数组的指针
    int a[3][4];
    p=a; //将二维数组的首地址赋值给p,也可是a[0]或&a[0][0]
    p++; //表示p跨过行a[0][],指向了行a[1][]

    所以,数组指针也成为指向一维数组的指针,也就是行指针。

  • 指针数组

    定义为:int p[n]*; (注意优先级:()>[]> *)

    []> * ,所以p是数组,是一个由n个指针类型元素组成的指针数组,或者说这个当一个数组里含有的元素为指针类型的时候,它就被成为指针数组。当p+1时,则p指向下一个数组元素。(需注意,p=a;这种赋值方法是错的,因为p是一个不可知变量,只存在p[0],p[1],p[2],但可以这样 p=a; 这里p表示指针数组第一个元素的值,a的首地址的值)

    将二维数组赋值给指针数组:

    1
    2
    3
    4
    int *p[3];     //定义指针数组
    int a[3][4];
    for(i=0;i<3;i++)
    p[i]=a[i]; //通过循环将a数组每行的首地址分别赋值给p里的元素

    这里int * p[3]表示一个一维数组内存放三个指针变量,分别是p[1],p[2],p[3]。如果要引用二维数组时,可以有多种表达方式:如表示数组中i行j列一个元素::* (* (p+i)+j)、(* (p+i) )[j]、p[i][j]、 * (p[i]+j)

(5) 指向二维数组的指针、多重指针和指针数组;

(6) 函数指针

动态分配内存·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
int * p = (int *)malloc(int len);
/*
1.本语句一共分配了两块内存,一块是动态分配的,总共 len 个字节,一个是静态分配的是 4 个字节,即变量 p 本身所占的内存。

2.malloc() 只有一个 int 型的形参,表示要求系统分配的字节数。

3.malloc() 函数的功能是请求系统分配 len 个字节的内存空间
如果请求分配成功,则返回第一个字节的地址,如果分配不成功,则返回 NULL。

4.malloc() 函数只能返回第一个字节的地址
所以我们需要把这个无任何实际意义的第一个字节的地址(俗称干地址)转化成一个有实际意义的地址
因此 malloc 前面必须加 (数据类型 *),表示把这个无实际意义的第一个字节的地址转化为相应类型的地址

calloc函数:void * calloc(unsigned n , unsigned size )
作用:在内存的动态存储区中分配n个长度为size的连续空间,这个空间一般比较大,足以保存一个数组。分配不成功,返回NULL.

freep(p);
表示将 p 所指向的内存给释放掉,p 本身的内存是静态的。
不能由程序员手动释放,p 本身的内存只能在 p 变量所在的函数运行终止时由系统自动释放
*/

8. C语言结构和联合·

(1) 结构:结构类型的定义和访问、包含结构的结构·

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
struct s {
int b;
double c;
char d[10];
};

s a;
//表示声明了一个名为 s 的结构体,这个结构体由一个 int 类型变量 b,一个 double 类型变量 c,一个 char 类型变量数组 d[10] 组成。接着,定义了一个名为 a 的结构体 s 变量;
// 弄清楚 a 和 s 的区别:
// s 是结构体的名称,本身只是一种声明而不占用任何存储空间,是自定义的一种数据类型;
// a 是数据类型为 s 的一个变量,每定义一个这样的变量,就会同时生成一组 b, c, d[10]。

struct s2 {
int b;
double c;
char d[10];
} a[100];
//a[100] 表示创建了一个数据类型为 s2,名为 a 的结构体数组,其中包含 100 个 s2 类型的结构体;

结构体与链表

1
2
3
4
5
6
struct Student {
int num;
double score;
struct Student *next;
};
//在结构体 Student 中定义一个 Student 指针变量,用于指向下一个 Student 元素,以此类推,就可以构建出一条链表。

(2) 联合union:联合类型的定义和访问

  • 各成员共用一块内存空间,并且同时**只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。**因而,联合体比结构体更节约内存。**一个union变量的总长度至少能容纳最大的成员变量,而且要满足是所有成员变量类型大小的整数倍。

(3) 类型定义语句(typedef)

​ typedef 用来为一个已有的数据类型定义一个别名,格式为:typedef 原类型名 新类型名;

可以用于原类型名太长而需要简化的情况,比如:

1
typedef long long ll;

枚举类型 enum :

1
2
3
4
5
6
7
8
9
10
11
enum Weekday {
sun, mon, tue, wed, thu, fri, sat
} a, b;
/*
Weekday 是枚举类型,a, b 是枚举变量,sum, mon, ... 是枚举常量;
 枚举变量可以且仅可以为枚举常量中的其中之一,比如 a = sun, b = thu, ...;
 枚举常量可以自行乱序赋初值,所有没有被赋初值的系统会按序逐一递增设置,直到下一个被赋初值的。 */
 enum Alphabet{a, b = 3, c, d, e, f = 10, g};

 //则 a, b, ..., g 的值分别是 0, 3, 4, 5, 6, 10, 11;
 //而上面的 Weekday 的常量的值分别是 0, 1, 2, ..., 6。

9. 输入/输出和文件·

(1) 输入/输出的基本过程和文件类型

  • 数据文件可以分为 ASCII文件(文本文件)和二进制文件(映像文件)

(2) 文件的打开、创建和关闭

  • 使用fopen函数来打开文件,fclose来关闭文件。

    1
    2
    3
    4
    //打开文件
    FILE * fopen ( const char * filename, const char * mode );
    //关闭文件
    int fclose ( FILE * stream );
文件使用方式 含义 如果指定文件不存在
"r"(只读) 为了输入数据,打开一个已经存在的文本文件 出错
"w"(只写) 为了输出数据,打开一个文本文件 建立一个新的文件
"a"(追加) 向文本文件尾添加数据 建立一个新的文件
"rb"(只读) 为了输入数据,打开一个二进制文件 出错
"wb"(只写) 为了输出数据,打开一个二进制文件 建立一个新的文件

(3) 文件数据的正文(文本)格式读写

  • 文件的输出/写入就是将数据写入到文件当中,而文件的输入/读取就是将文件中的内容读取到内存当中。

    功能 函数名 适用于
    字符输入函数 fgetc 所有输入流
    字符输出函数 fputc 所有输出流
    文本行输入函数 fgets 所有输入流
    文本行输出函数 fputs 所有输出流
    格式化输入函数 fscanf 所有输入流
    格式化输出函数 fprintf 所有输出流
    二进制输入 fread 文件
    二进制输出 fwrite 文件

(4) 读写操作中的定位

  • fseek() 改变文件位置指针的位置
  • ftell() 返回文件位置指针的当前值

(5) 文件数据的二进制格式读写

C语言程序从编写到运行历经的几个阶段

预处理

​ 预处理过程实质上是处理“#”,将#include包含的头文件直接拷贝到hell.c当中;将#define定义的宏进行替换,同时将代码中没用的注释部分删除等

编译

​ 编译的过程实质上是把高级语言翻译成汇编语言

汇编(Assembly) 汇编阶段是将汇编语言代码转换成目标文件的过程。这个阶段由汇编器完成,它会将汇编语言代码转换成机器代码,

链接(Linking) 链接阶段是将多个目标文件和库文件合并成一个可执行文件的过程。