系列连载
参考
- 《C语言程序设计(第四版)谭浩强》
- 【C语言】C语言视频教程
- 《郝斌 C 语言自学教程》
C 语言概述
A.1 为什么学习 C 语言
1)C 的起源和发展
2)C 的特点
- 优点
代码量小、速度快、功能强大 - 缺点
危险性高、开发周期长、可移植性不强(相对 java)
3)C 的应用领域(系统软件)
4)C 的重要性
A.2 怎样学习 C 语言
多思考、多上机
A.3 学习的目标
A.4 常见问题答案
1)学习 java 为什么建议先学 C 语言
2)没学过计算机专业课程能够学懂 C 语言?
3)英语和数学不好能学好 C 么?
A.5 课程计划
A.6 举例:一元二次方程
#include<stdio.h>
#include<math.h>
int main(void)
{
int a = 1;
int b = 5;
int c = 6;
double delta = b*b - 4 * a*c;
double x1;
double x2;
if (delta > 0)
{
x1 = (-b + sqrt(delta)) / (2 * a);
x2 = (-b - sqrt(delta)) / (2 * a);
printf("方程有两个解,分别为 x1 = %f, x2 = %f\n", x1, x2);
}
else if (delta == 0)
{
x1 = x2 = -b / (2 * a);
printf("方程有一个解, x1 = x2 = %f\n", x1);
}
else
{
printf("方程没有解!\n");
}
return 0;
}
C 语言编程预备知识
B.1 cpu、内存条、硬盘、主板、显卡之间的关系
比如看电影
电影存在硬盘上
双击电影,copy 调入到内存条中,CPU 对内存条中的电影处理
通过声卡和显卡播放出来
CPU、内存条、硬盘、显卡都在主板上
B.2 Hello World 程序如何运行起来的
compile(编译) -> build (链接) 生成 .exe
buildExecute(操作系统调用 CPU,执行 .exe)
B.3 什么是数据类型
基本类型数据
- 整数
整型 – int 4
短整型 – short int 2
长整型 – long int 8 - 浮点数(实数)
- 单精度浮点数 – float 4
- 双精度浮点数 – double 8
- 字符
char 1
复合类型数据
- 结构体
- 枚举
- 共用体
B.4 什么是变量
本质上是内存中一段存储空间
B.5 cpu、内存条、vc++6.0、操作系统之间的关系
int i = 3;
vc++6.0 请求操作系统在内存条中分配空间,与变量 i 之间产生关联
B.6 变量为什么必须初始化
所谓初始化就是赋值的意思
B.7 如何定义变量
B.8 什么是进制
逢 x 进一
10 进制转成其它进制
#include<stdio.h>
int main(void)
{
int i = 88;
printf("i= %d\n", i); //以 10 进制输出
printf("i= %o\n", i); // 以 8 进制输出
printf("i= %x\n", i); // 以 16 进制输出
return 0;
}
output
i= 88
i= 130
i= 58
16 进制转成其它进制
#include<stdio.h>
int main(void)
{
int i = 0x11;
printf("i= %d\n", i); //以 10 进制输出
printf("i= %o\n", i); // 以 8 进制输出
printf("i= %x\n", i); // 以 16 进制输出
return 0;
}
output
i= 17
i= 21
i= 11
B.9 常量在 C 语言中是如何表示的
“A” 为 ‘A’ 和 ‘\0’
#include<stdio.h>
int main(void)
{
char ch = 'A';
char ch = 'B';
return 0;
}
会报错
error C2374: “ch”: 重定义;多次初始化
B.10 常量以什么样的二进制代码存储在计算机里
字符->十进制整数(通过 ASCII)->二进制
B.11 代码规范化
成对,换行,缩进,空格
代码的可读性更强
使程序更不容易出错
B.12 什么是字节
字节就是存储数据的单位,并且是硬件所能访问的最小单位
位可以通过运算符以软件的形式操作
B.13 不同类型数据之间相互赋值的问题
涉及到溢出,暂时不考虑
#include<stdio.h>
int main(void)
{
int i = 0X7FFFFFFF;
int j = 0X7FFFFFFF+1;
int k = 0X7FFFFFFF + 2;
int l = sizeof(i);
printf("%d\n", l);
printf("%d %d %d", i,j,k);
return 0;
}
output
4
2147483647 -2147483648 -2147483647
B.14 什么是 ASCII
不是一个值,而是一种规定
规定了不同的字符是使用哪个整数去表示
‘A’-65
‘B’-66
‘a’-97
‘b’-98
‘0’-48
GB3312码
UTF-8码
B.15 字符的存储本质上与整数的存储方式相同
1 基本概念
1.1 C语言的起源
C语言是在70 年代初问世的。一九七八年由美国电话电报公司(AT&T)贝尔实验室正式发表了C语言。同时由B.W.Kernighan和D.M.Ritchit合著了著名的“THE C PROGRAMMING LANGUAGE”一书。通常简称为《K&R》,也有人称之为《K&R》标准。但是,在《K&R》中并没有定义一个完整的标准C 语言,后来由美国国家标准协会(American National Standards Institute)在此基础上制定了一个C 语言标准,于一九八三年发表。通常称之为ANSI C。
1.2 C语言中使用的词汇分类
在C语言中使用的词汇分为六类:
- 标识符:在程序中使用的变量名、函数名、标号等统称为标识符。(字母或者下划线开头)
- 关键字
- 类型说明符(int double)
- 语句定义符(if else)
- 预处理命令字(include)
- 运算符
- 分隔符(逗号和空格)
- 常量
- 注释符等。
1.3 程序
一个程序应包括:
- 对数据的描述。在程序中要指定数据的类型和数据的组织形式,即数据结构(data structure)。
- 对操作的描述。即操作步骤,也就是算法(algorithm)——做任何事情都有一定的步骤。为解决一个问题而采取的方法和步骤,就称为算法。。
数据结构+算法=程序
如何理解数据结构和算法的关系,根据实际问题去设计数据结构,在数据结构的基础上进行算法设计。即,数据结构特点决定了算法的设计。
结构化程序设计方法:
- 自顶向下;
- 逐步细化;
- 模块化设计;
- 结构化编码。
1.4 算法的特性
- 有穷性:一个算法应包含有限的操作步骤而不能是无限的。
- 确定性:算法中每一个步骤应当是确定的,而不能应当是含糊的、模棱两可的。
- 有零个或多个输入。
- 有一个或多个输出。
- 有效性:算法中每一个步骤应当能有效地执行,并得到确定的结果。
对于程序设计人员,必须会设计算法,并根据算法写出程序。
1.5 算法的表示方法
- 用自然语言表示算法
- 用流程图表示算法
- 用 N-S 流程图表示算法(这个没有用过)
- 用伪代码表示算法(伪代码使用介于自然语言和计算机语言之间的文字和符号来描述算法。)
- 用计算机语言表示算法(eg:C, C++, python)
下面以流程图表示算法为例
eg:
注意平行四边形是输入输出框,这个我用的比较少!!!
可以用 顺序结构、循环结构、选择结构,三种基本结构共同特点
- 只有一个入口;
- 只有一个出口;
- 结构内的每一部分都有机会被执行到;
- 结构内不存在“死循环”。
2 工具 + 牛刀小试
2.1 hello world
#include<stdio.h>
void main()
{
printf("hello, world!\n");
}
2.2 求 sin
这里是弧度,联想 s i n ( π / 2 ) = 1 sin(\pi/2) = 1 sin(π/2)=1 即可,弧度向度数转换的公式为乘上 180 / π 180/\pi 180/π
#include<stdio.h>
#include<math.h>
void main()
{
double x,s;
printf("input number:\n");
scanf("%lf",&x);
s=sin(x);
printf("sine of %lf is %lf\n",x,s);
}
2.3 两个数比较大小
#include<stdio.h>
#include<math.h>
int max(int a, int b);
void main()
{
int x,y,z;
int max(int a, int b);
printf("input two numbers:\n");
scanf("%d%d",&x,&y);
z = max(x,y);
printf("maxmum=%d\n",z);
}
int max(int a, int b)
{
if (a>b)
return a;
else
return b;
}
3 数据类型
图片来源:https://www.bilibili.com/video/av2831981/?p=2
对于基本数据类型,按照其值是否可以改变,可以分为,常量和变量
3.1 常量和符号常量
在程序执行过程中,其值不发生改变的量称为常量
符号常量:用标示符代表一个常量。其一般形式为 #define 标识符 常量
#include<stdio.h>
#define Price 30
void main()
{
int num, total;
num = 10;
total = num*Price;
printf("total=%d\n",total);
}
预处理命令,在没有编译之前,把所有的 Price
都替换成了 30
使用符号常量的好处是:
- 含义清楚;
- 能做到“一改全改”
3.2 变量
int k = 3;
图片来源:https://www.bilibili.com/video/av2831981/?p=2
3.3 整型
3.3.1 整型常量
整型常量的表示方法有如下三种:
- 八进制:0 开头(零)
- 十进制
- 十六进制:0x 或者 0X(零)
3.3.2 整型变量
整型数据在内存中的存放形式:
数值是以补码表示的
- 正数的补码与原码相同;
- 负数的补码:将该数的绝对值的二进制形式按照按位取反再加1
下面看一个求 8 位二进制补码的例子!
补码的由来,为啥是 -128~127(±0)
与或非(道生一,一生二,二生三,三生万物)
用一个与门和一个异或门就可以实现二进制加法
整型变量的分类
- int
- short int 或 short
- long int 或 long
- unsigned
unsigned 又可以和上面三个组合起来!
占多少字节跟系统和编译器规定有关!可以在编译器上自己试试查看,用 sizeof
!!!
#include<stdio.h>
void main()
{
printf("short:%d\n",sizeof(short));
printf("short int:%d\n",sizeof(short int));
printf("int:%d\n",sizeof(int));
printf("long:%d\n",sizeof(long));
printf("long int:%d\n",sizeof(long int));
printf("unsigned:%d\n",sizeof(unsigned));
printf("unsigned short:%d\n",sizeof(unsigned short));
printf("unsigned int:%d\n",sizeof(unsigned int));
}
int
的范围,4字节32位,
−
2
31
-2^{31}
−231 ~
2
31
−
1
2^{31}-1
231−1(为什么减1,因为0),也即 -2147483648 ~ 2147483647,其它的以此类推!
unsigned
,
0
0
0 ~
2
32
−
1
2^{32}-1
232−1,0~4294967295,也即 0x0~0xffff ffff
来个简单的例子
#include<stdio.h>
void main()
{
int a,b,c,d;
unsigned u;
a = 12, b =-24, u = -10;
c = a+u;
d = b+u;
printf("%a+u=%d,b+u=%d\n",c,d);
}
纳尼?unsigned 还可以赋值负数?
%d
有符号10进制整数%u
无符号10进制整数
换成 %u 试试,修改 printf("%a+u=%u,b+u=%u\n",c,d);
哈哈,现出了庐山真面目,
2
32
2^{32}
232 -34 = 429496726
试试,b+u = -1 的例子,
2
32
2^{32}
232 -1 = 4294967295
再来个简单的例子看看溢出的问题
#include<stdio.h>
#include<math.h>
void main()
{
short int a,b;
a = pow(2,8*sizeof(short int)-1)-1;
b = a + 1;
printf("%d,%d\n",a,b);
}
根据前面的例子我们知道,short int 两个字节,范围为
−
2
15
-2^{15}
−215~
2
15
−
1
2^{15}-1
215−1,
2
15
2^{15}
215为 32768,上面的例子我们让 a 被赋值为最大的数,加一后的 b 竟然成了负数,也就是溢出了,原理如下!最高位符号位,0 表示正数,1表示负数!计算机用补码的形式存储数据,32767+1 算出来的正好是 -32768,用前面所讲的公式计算,取反+1
3.4 浮点型(实型)
3.4.1 浮点型常量的表示方法
- 十进制数形式
- 指数形式(阶码标志,e 或者 E,科学计数法)
在计算机中是按照指数形式存储的,浮点型常数不分单、双精度,都按双精度double型处理
3.4.2 实型变量
1)实型数据在内存中的存放形式
- 小数部分的位数越多,精度越高
- 指数部分的位数越多,数值范围越大
小数部分有23bit, 2 23 = 8388608 2^{23} = 8388608 223=8388608,float 的有效数字为 6-7 位,超过了就无法精确了!
关于 float 的取值范围参考 IEEE标准中32位、64位浮点数的取值范围
这里仅给出结论
2)实型变量的分类
- float
- double
- long double
字节数分别如下
3)浮点型数据的舍入误差
前面说了有效数字是7位
#include "stdio.h"
void main()
{
float a,b;
a = 123456.789e5;
b = a + 20;
printf("%f\n",a);
printf("%f\n",b);
}
口算的话,结果为
- 12345678900
- 12345678920
可是计算机计算出来的结果如下
float 32 位能保证前6位都是精确的,部分第七位也行,第八位也未必不一样,具体解释如下 关于float型是单精度的有效位数是7位,为什么在下面的例子中这8位都是准确的呢?
#include"stdio.h"
void main(){
printf("%f\n",1.0/3*3);
printf("%f\n",1/3*3);
printf("%f\n",3/2);
printf("%f\n", (float)(3/2));
printf("%d\n", 3/2);
}
output
1.000000
0.000000
0.000000
1.000000
1
3.5 字符型数据
3.5.1 字符型常量
3.5.2 字符型变量
#include"stdio.h"
void main(){
int a = 120;
int b = 121;
char c = 'z';
printf("%c, %c, %c\n",a,b,c);
printf("%d, %d, %d\n",a,b,c);
}
output
x, y, z
120, 121, 122
试试把小写字母转化为大写字母,查 ASCII 码表知道,‘A’ 是 65,‘a’ 是 97,相隔 32,我们可以利用这个 32,来实现大小写字母的转换!
#include "stdio.h"
void main()
{
char a,b;
a = 'a';
b = 'b';
a = a - 32; //对应着 ASCII 码为 65,是 A
b = b - 32; //对应着 ASCII 码为 66,是 B
printf("%c %c\n%d %d\n", a, b, a, b);
}
output
A B
65 66
3.6 字符串型数据
3.6.1 字符串常量
3.7 各类数据类型之间的混合运算
- 自动转换(精度不降低的原则)
- 强制转换
1)自动转换
注意 float 都是转化为 double 来计算的
2)强制转换
注意第二条,类型转换只是临时的(孙悟空72变,但是还是会变回孙悟空的)!
#include "stdio.h"
void main()
{
float PI = 3.1415926;
printf("%d, %f\n", int(PI), PI);
}
output
3, 3.141593
4 基本的输入输出函数的用法
1)printf()——将变量的内容输出到显示器上
四种用法
- printf(“字符串\n”);
- printf(“输出控制符”,输出参数);
- printf(“输出控制符1 输出控制符2…”,输出参数1,输出参数2)
输出控制符和输出参数个数必须一一对应 - printf(“输出控制符 非输出控制符”, 输出参数)
输出控制符包含如下
%d – int
%ld – long int
%c – char
%f – float
%lf – double
%x(或者%X或者%#X)——%#x 输出 0X16进制 – 16进制输出
%o – 8 进制
%s
#include<stdio.h>
int main(void)
{
int i = 100;
int k = 11;
printf("%#x %d\n", i, k);
return 0;
}
output
0x64 11
为什么需要输出控制符
- 01 组成的代码可以表示数据也可以表示指令
- 如果 01 组成的代码表示的是数据的话,那么同样的 01 代码组合以不同的输出格式输出就会有不同的输出结果
scanf()
2)scanf()——通过键盘讲数据输入到变量中
两种用法
用法一 scanf(“输入控制符”,输入参数)
功能,将从键盘输入的字符转化为输入控制符所规定的数据,然后存入以输入参数的值为地址的变量中
#include<stdio.h>
int main(void)
{
int i;
scanf("%d", &i); // &i 表示取 i 的地址
printf("i = %d\n", i);
return 0;
}
output
145
i = 145
用法二 scanf(“非输入控制符 输入控制符”,输入参数)
功能,将从键盘输入的字符转化为输入控制符所规定的数据,然后存入以输入参数的值为地址的变量中
非输入控制符必须原样输入
#include<stdio.h>
int main(void)
{
int i;
scanf("m%d", &i);
printf("i = %d\n", i);
return 0;
}
测试1
123 // 输入 123,i 的没有被初始化
i = -858993460
测试2
m123
i = 123
测试1
m123n123m
i = 123
如何使用 scanf 编写出高质量代码
1、使用 scanf 之前最好先使用 printf 提示用户以什么样的方式来输入
2、scanf 中尽量不要使用非输入控制符,尤其是不要用 \n
3、应该编写代码对用户的非法输入做适当的处理
一次输入多个
#include<stdio.h>
int main(void)
{
int i,j,k;
printf("请输入三个值,中间用逗号隔开!\n")
scanf("%d,%d,%d", &i, &j, &k);
printf("i = %d, j = %d, k = %d\n", i, j, k);
return 0;
}
out
请输入三个值,中间用逗号隔开:
1,2,3
i = 1, j = 2, k = 3
读到不是整数的时候,之后的变量就会随机初始化
#include<stdio.h>
int main(void)
{
int i, j;
scanf("%d", &i);
printf("i = %d\n", i);
// ...
scanf("%d", &j);
printf("j = %d\n", j);
return 0;
}
test1
12
i = 12
34
j = 34
test2
123m
i = 123
j = -858993460
读完 123 后读到了 m,给 j 随机初始化了
改进
#include<stdio.h>
int main(void)
{
int i, j;
char ch;
scanf("%d", &i);
printf("i = %d\n", i);
// ...
while ((ch = getchar()) != '\n')
continue;
scanf("%d", &j);
printf("j = %d\n", j);
return 0;
}
测试
123m
i = 123
4
j = 4