1 引言
1.1 C语言的发展史
- 1960 原型A语言 -> ALGOL语言
- 1963 CPL语言
- 1967 BCPL语言
- 1970 B语言 B语言+汇编编写了Unix
- 1973 C语言 C语言重新编写了Unix
1.2 C语言的特点
- 基础性语言
- 语法简洁,紧凑,方便,灵活
- 运算符丰富,数据结构丰富
- 结构化,模块化编程
- 移植性好,执行效率高
- 允许直接对硬件操作
1.3 可执行文件的生成
C源文件 — 预处理器 — 编译器 — 汇编器 — 链接器 — 可执行文件
2 基本概念
写程序的注意事项
- 头文件必须得正确包含
- 以函数为单位来进行程序的编写
- 程序包含声明部分+实现部分
- return 0
- 多用空格和空行
- 适当添加注释
算法
算法即解决问题的方法(流程图, NS图, 有限状态机FSM)
程序
用某种语言实现算法
防止写越界,防止内存泄漏,谁打开谁关闭,谁申请谁释放
3 数据类型,运算符和表达式
3.1 数据类型(基本数据类型)
3.1.1 概述
- 基本类型
- 数值类型
- 整数 short, int, long
- 浮点型 float, double
- 字符型 char
- 数值类型
- 构造类型
- 数组
- 结构体 struct
- 共用体 union
- 枚举类型 enum
- 指针类型
- 空类型 void
3.1.2 基本数据类型
以下大小是在64位机器上的大小
数值类型
- 整数
- 有符号数 short(2B), int(4B), long(8B)
- 无符号数 unsigned short(2B), unsigned(4B), unsigned long(8B)
- 浮点数
- 单精度 float(4B)
- 双精度double(8B)
字符型
- 有符号 char(1B)
- 无符号unsigned char(1B)
基本数据类型在底层是以二进制的形式进行存储的,其中有符号数是以补码的形式进行存储的
补码的最高位是符号位,1表示负数,0表示整数,最高位的权值为-2 ^ i,其他位的权值为2 ^ i,正数的补码就是其本身,负数的补码是其绝对值按位取反后末尾+1
3.1.3 数据类型的转换
- 隐式类型转换,也成为自动类型转换,比如在做数据的运算时会向精度高的进行转换
- 强制(显式)类型转换
3.1.4 特殊类型
- 布尔型bool
- float类型并不是一个精确的值
- char型是否有符号
3.2 常量与变量
常量:在程序执行过程中值不会发生变化的量
分类:
-
整型常量
-
浮点型常量
-
字符常量 : 由单引号引起来的单个字符或转义字符,如’a’, ‘\0’, ‘\015’标识一个8进制数,’\x7f’表示一个16进制数
-
字符串常量 : 由双引号引起来的一个或多个字符组成的序列,如 “hello”, “”(空串只有一个尾0)
-
标识常量 #define
-
预处理的时候就会将所有的宏名替换为宏体,并不会去检查语法错误,同时注意宏体一定要加上扩号,比如下面ADD未加上括号导致输出并不出想要的5 * 5
#include <stdio.h> #define ADD 2 + 3 int main() { int ans = ADD * ADD; printf("%d", ans);//输出11 return 0; }
-
#define可以定义一些函数,宏相对于函数来讲,是比较危险的,但是能够节省运行时间,因为在编译阶段就已经完全替换掉了,比如内核中模块多用的是宏,应用层中要求稳定应用函数
#define MAX(a, b) ((a) > (b) ? (a) : (b)) int max(int a, int b) { return a > b ? a : b; } int main() { int ans = MAX(1, 4); printf("%d", ans);//输出4 return 0; }
-
#define可以定一个代码段
#include <stdio.h> #define MAX(a, b) \ ({ int A = a, B = b; ((A) > (B) ? (A) : (B));}) int main() { int ans = MAX(1, 4); printf("%d", ans);//输出4 return 0; }
-
变量:用来保存一些特定的内容,在程序执行过程中值随时会发生变化的量
定义: 【存储类型】 数据类型 标识符 = 值
TYPE NAME = VALUE;
- 标识符 就是一个表示变量的序列
- 数据类型 基本数据类型+构造类型
- 存储类型 auto static register extern(说明型关键字)
- auto 默认,自动分配空间,自动释放空间
- register 寄存器型,该型不能取址,且只能定义局部变量,同时是一个建议型关键字,至于该型是否真的放到寄存器中由编译器来决定
- static 静态型,自动初始化为0值或空值,并且这种类型变量的值有继承性(该变量只被定义一次),另外常用来修饰变量或函数表示是当前文件私有的
- extern 说明型关键字,意味着不能改变被说明的变量的值和类型
变量的生命周期和作用域
-
全局变量和局部变量
全局变量的作用域是从定义的位置开始到程序结束,局部变量的作用范围是从定义的位置开始,到期所在的块结束处,同时内部的会屏蔽外部的,即就近原则
int i = 100; int main() { int i = 1; printf("%d", i); // 结果为1 return 0; }
-
局部变量和局部变量
static与auto
3.3 运算符和表达式
注意事项
- 每个运算符所需要的参与运算的操作数的个数
- 结合性
- 优先级
- 运算符的特殊使用
- 位运算的重要意义
C运算符
- 算数运算符 +, -, *, /, %, ++, –
- 关系运算符 >, >=, <, <=, !=, ==
- 逻辑运算符 !, &&, || (具有短路特性)
- 位运算符 >>, <<, &, | ~, ^, >>>
- 赋值运算符 =, +=, -=, *=, /=, %=等等
- 条件(三目)运算符 ? :
- 逗号运算符 ,
- 指针运算符 *, &
- 求字节数 sizeof
- 强制类型转换 (类型)
- 分量运算符 ., ->
- 下标运算符 []
- 其他 ()
几种典型的位运算
- 将操作数中的第n位置1,其他位不变:num = num | (1 << n);
- 将操作数中的第n位置0,其他位不变:num = num & (~(1 << n));
- 测试第n位:num & (1 << n);
- 从一个指定宽度的数中取出其中的某几位(比如取出第i到第j位):num & ((~(-1 << (j - i + 1))) << i)
4 出入、输出专题
4.1 格式化的输入输出scanf, printf
printf
函数定义
/**
* extern int printf (const char *__restrict __format, ...);
* __restrict__format : "%[修饰符]格式字符"
* 函数的返回值是答应出来的字符串的长度,不包括尾0
*/
示例
#include <stdio.h>
int main() {
printf("%%d的使用: %d\n", 123); //输出 %d的使用: 123
printf("%%x的使用: %x\n", 123); //输出 %x的使用: 7b
printf("%%o的使用: %o\n", 123); //输出 %o的使用: 173
printf("%%u的使用: %u\n", 123); //输出 %u的使用: 123
printf("%%c的使用: %c\n", 65); //输出 %d的使用: %c的使用: A
printf("%%s的使用: %s\n", "65"); //输出 %d的使用: %s的使用: 65
printf("%%e的使用: %e\n", 123.456); //输出 %d的使用: %e的使用: 1.234560e+02
printf("%%f的使用: %f\n", 123.456); //输出 %d的使用: %f的使用: 123.456000
printf("%%g的使用: %g\n", 123.456); //输出 %d的使用: %g的使用: 123.456
return 0;
}
修饰符
注意事项,printf的缓冲区刷新,\n换行符能够强制进行输出缓冲区的刷新
加上换行符的效果
#include <stdio.h>
#include <unistd.h>
int main() {
printf("[%s:%d]\n", __FUNCTION__ , __LINE__);
sleep(5);
printf("[%s:%d]\n", __FUNCTION__ , __LINE__);
return 0;
}
不加换行符的效果
#include <stdio.h>
#include <unistd.h>
int main() {
printf("[%s:%d]", __FUNCTION__ , __LINE__);
sleep(5);
printf("[%s:%d]", __FUNCTION__ , __LINE__);
return 0;
}
可以看到不加换行符的时候是将缓冲区中的输出在程序退出的时候一起输出
scanf
函数定义
/**
* extern int scanf (const char *__restrict __format, ...) __wur;
* __restrict__format : "%[修饰符]格式字符", 后面的...是地址
* 函数的返回值是成功接收到值的个数
*/
示例
#include <stdio.h>
int main() {
int i;
printf("Please enter: \n");
scanf("%d", &i);
printf("i = %d\n", i);
return 0;
}
4.2 字符输入输出getchar, putchar
getchar
函数定义
int getchar (void)
putchar
函数定义
int putchar (int __c)
示例
#include <stdio.h>
int main() {
int ch;
ch = getchar();
putchar(ch);
return 0;
}
4.3 字符串输出输出gets, puts
gets
函数定义
gets并不会检查缓冲区是否溢出
char *gets (char *__s)
puts
函数定义
int puts (const char *__s)
示例
#include <stdio.h>
#define STRSIZE 32
int main() {
char str[STRSIZE];
gets(str);
puts(str);
return 0;
}
4.4 练习题
练习题一
一个水分子的质量大约为3.0e-23g, 一夸脱水大约有950g,编写一个程序,要去从终端输入水的夸脱数,然后显示这么多夸脱水中包含有大概多少水分子。
#include <stdio.h>
#define K 950
#define ATOM (3.0e-23)
int main() {
double weight;
double cnt;
puts("请输入水的夸脱数: ");
scanf("%lf", &weight);
cnt = weight * K / ATOM;
printf("这么多水中含有的水分子数为: %e\n", cnt);
return 0;
}
练习题二
从终端输入三角形的三边长,求面积
s = 1 / 2 * (a + b + c);
area = sqrt(s * (s - a) * (s - b) * (s - c));
#include <stdio.h>
#include <math.h>
int main() {
float a, b, c;
puts("请输入三角形的三个边长: ");
int ret = scanf("%f%f%f", &a, &b, &c);
if (ret != 3) {
puts("输入的边数必须是3");
return 0;
}
if (a + b <= c || a + c <= b || c + b <= a) {
puts("输入的三边不能构造成三角形!!!");
return 0;
}
float s = (a + b + c) / 2;
printf("%f", s);
float area = sqrt(s * (s - a) * (s - b) * (s - c));
printf("三角形的面积为 %f", area);
return 0;
}
5 流程控制
5.1 概述
程序分为三种结构 顺序,选择,循环,所涉及到的关键字如下:
- 顺序
- 选择: if-else, switch-case
- 循环: while, do-while, for, if-goto
- 辅助控制: continue, break
5.2 选择结构
5.2.1 if-else
if (exp)
cmd;
或者
if (exp)
cmd;
else if (exp)
cmd;
else
cmd;
同一级别的if-else if-else只会匹配其中的一个
注意,else只与最近的if进行匹配(在没有代码块括号的情况下),比如下面代码,会输出a != b,因为else是和第二个if匹配上了(分支语句超过一句了就加上{})
int main() {
int a = 1, b = 1, c = 2;
if (a == b)
if (a == c)
printf("a == b == c\n");
else
printf("a != b\n");
return 0;
}
将如上代码做以下修改,则什么也不会输出
int main() {
int a = 1, b = 1, c = 2;
if (a == b) {
if (a == c)
printf("a == b == c\n");
} else
printf("a != b\n");
return 0;
}
练习 输出分数对应的等级
#include <stdio.h>
int main() {
double score;
char class;
puts("请输入你的考试成绩: ");
scanf("%lf", &score);
if (score < 0 || score > 100) {
puts("成绩应该为不小于0且不大于100的数");
return 0;
}
if (score >= 90 && score <= 100) class = 'A';
else if (score >= 80) class = 'B';
else if (score >= 70) class = 'C';
else if (score >= 60) class = 'D';
else class = 'E';
printf("您成绩的级别为: %c", class);
return 0;
}
5.2.2 switch-case
switch (exp) {
case 常量表达式:
cmd;
break; //当没有break时会一直向下判断直到遇到break
case 常量表达式:
cmd;
break;
......
default:
cmd;
}
利用switch-case重新写成绩顶级的逻辑:
#include <stdio.h>
int main() {
int score;
char class;
puts("请输入你的考试成绩: ");
scanf("%d", &score);
if (score < 0 || score > 100) {
puts("成绩应该为不小于0且不大于100的数");
return 0;
}
switch (score / 10) {
case 10:
case 9:
puts("A");
break;
case 8:
puts("B");
break;
case 7:
puts("C");
break;
case 6:
puts("D");
break;
default:
puts("E");
}
return 0;
}
5.3 循环结构
5.3.1 while
while (exp) {
//最少执行0次
loop;
}
练习 求1-100和
#include <stdio.h>
int main() {
int sum = 0;
int i = 0;
while (i <= 100) sum += i++;
printf("sum = %d", sum);
return 0;
}
5.3.2 do-while
do {
//最少执行1次
loop;
} while (exp);
练习 求1-100和
#include <stdio.h>
int main() {
int sum = 0;
int i = 0;
do {
sum += i++;
} while (i <= 100);
printf("sum = %d", sum);
return 0;
}
5.3.3 for
for (exp1; exp2; exp3) {
// 最少循环次数为0次
loop;
}
练习 求1-100和
#include <stdio.h>
int main() {
int sum = 0;
int i = 1;
for (; i <= 100; i++) {
sum += i;
}
printf("sum = %d", sum);
return 0;
}
5.3.4 if-goto
该结构慎用,因为goto表示无条件跳转,且不能跨函数跳转
#include <stdio.h>
int main() {
int sum = 0;
int i = 1;
loop:
sum += i;
i++;
if (i <= 100)
goto loop;
printf("sum = %d", sum);
return 0;
}
5.3.5 辅助控制
break 表示跳出