C语言知识点自学总结
前言
自学C语言,做一个知识总结
参考教程地址中国大学mooc 翁凯C语言
一、计算机和编程语言
一、计算机语言
程序是用特殊的编程语言写出来表达如何解决问题的。
编程语言不是和计算机交谈的语言,而是描述要求计算机做事情的过程或方法。
二、算法
我们要让计算机做计算,就要找出计算的的步骤,然后用编程语言写出来。
计算机做的所有事情都叫做计算。
三、程序的执行
解释:借助一个程序,那个程序能试图理解你的程序,然后按照你的要求执行。
编译:借助一个程序,就像一个翻译,把你的程序翻译成计算机能懂的语言——机器语言——写到程序,然后,这个机器语言写的程序就能直接执行了。
四、解释语言和汇编语言
计算机不能直接的理解高级语言,只能直接理解机器语言,所以必须要把高级语言翻译成机器语言,计算机才能执行高级语言的编写的程序。翻译的方式有两种,一个是编译,一个是解释。
解释性语言是指它常用的执行机制是使用一个“解释器”来执行,解释器对于程序是一句一句“翻译”成机器语言来一句一句执行。解释型语言有特殊的计算能力
编译型语言是指它常用的执行机制是使用一个“编译器”来编译成机器语言,然后你就可以直接运行(执行)这个编译成的“可执行文件”。编译型语言有确定的运算性能。
各种编程语言本质没什么不同。所谓的“解释性”和“编译”指的是执行机制上的不同。
二、C语言基础入门
0.C语言概述
一、为什么是C
C很强
现代编程语言差异很小,几乎都是类C语言。
语言的能力和适用领域主要由库和传统所决定的。
二、C的用途
操作系统
嵌入式系统
驱动程序
底层驱动(图像引擎,图像处理,声音效果)
三、C的编程环境
C需要被编译才能被运行,所以需要编辑器,编译器或者IDE((Integrated Development Environment)
推荐编程软件:DevC++(简单好安装,,体积小,新手首选),MS Visual studio(宇宙第一IDE,功能太强,组件太丰富,占内存大),VScode(小巧轻便,支持多种语言,但要自己配置相应环境,麻烦)
1.变量和常量
一、输入输出
1、输入格式:scan(""); 如果输入数字,用scan("%d",&number),%d表示要输入的是数字,输入的数字存给number这个变量,""内是要输入的内容。输入是以行为单位的,行的结束标志是你按下回车键,在按下回车键之前,程序不会读到你输入的任务东西。
2、输出格式:printf(""); 如果输出数字,用printf("%d",number),%d表示要输出的是数字,输出的数字是number这个变量,用number替换%d,""内是要输出的内容。【注意】有无&的区别
二、变量
1、定义:顾名思义,一个可以变的量,用来保存数据的地方,数据可变。定义格式 <数据类型><变量名称>; 例:int amount;
2、名称:变量名叫标识符。用来区别标识。
3、命名规则:标识符只能由字母、数字和下划线组成的,首字符不能是数字,C语言的关键字不能用作标识符,标识符严格区分大小写。
4、常见关键字
C语言一共有32个关键字,根据关键字的作用,可分其为数据类型关键字、控制语句关键字、存储类型关键字和其它关键字四类。
12个数据类型关键字
关键字 | 意义 |
---|---|
char | 声明字符型变量或函数 |
short | 声明短整数类型变量或函数 |
int | 声明整型变量或函数 |
long | 声明长整型变量或函数 |
float | 声明字符型变量或函数 |
double | 声明双精度变量或函数 |
signed | 声明有符号类型变量或函数 |
unsigned | 声明无符号类型变量或函数 |
enum | 声明枚举类型 |
struct | 声明结构体变量或函数 |
union | 声明共用体(联合)数据类型 |
void | 声明函数无返回值或无参数,声明无类型指针 |
12个控制语句关键字 | |
关键字 | 意义 |
:- | :- |
if | 条件语句 |
else | 条件语句否定分支(与 if 连用) |
for | 一种特别的循环语句 |
do | 循环语句的循环体 |
while | 循环语句的循环条件 |
switch | 用于开关语句 |
break | 结束循环 |
continue | 结束当前循环,开始下一轮循环 |
case | 开关语句分支 |
default | 开关语句中的“其他”分支 |
goto | 无条件跳转语句 |
return | 返回语句(可以带参数,也可不带参数) |
4个存储类型关键字 | |
关键字 | 意义 |
:- | :- |
auto | 声明字符型变量或函数 |
extern | 对外声明引用 |
register | 声明寄存器变量 |
static | 声明静态变量 |
4个其他类型关键字 | |
关键字 | 意义 |
:- | :- |
const | 常类型,即声明一个只读变量 |
sizeof | 计算数据类型容量 |
typedef | 定义别名 |
volatile | 取消编译器优化,不使用缓存,说明变量在程序执行中可被隐含地改变 |
三、常量
常量是固定不变的量,不能赋值修改,变量可以赋值修改,(赋值符号是=,注意与数学里=含义不同,C中两个=才是等于,一个=是赋值)
const 是一个修饰符,例 const int i=1,i就变成了一个常量
给个C样例
#include <stdio.h> int main() { printf("请分别输入身高的英尺和英寸," "如输入\"5 7\"表示5英尺7英寸:"); double foot; double inch; scanf("%lf %lf", &foot, &inch); printf("身高是%f米。\n", ((foot + inch / 12) *0.3048)); return 0; }
补充\n是转义字符。表示换行
2.计算
一、数据类型
1、浮点数:带小数点的数。人们借用浮点数来表达所有带小数点的数。两个整数的计算结果只能是整数,当浮点数和整数在一起,C会将整数转换成浮点数,进行浮点数的计算。
double表示双精度浮点数,float表示单精度浮点数。
2、格式
整数输入输出格式printf("%d",..) scanf("%d",&...)
带小数点的数printf("%f",...) scanf("%lf",&...)
一个样例
#include <stdio.h> int main() { int a,b; scanf("%d %d", &a, &b); double c = (a+b)/2.0; printf("%d和%d的平均值=%f\n", a, b, c); return 0; }
二、表达式
1定义:一个表达式是一系列运算符和算子的组合;运算符是指运算的动作,如+、-;算子是参与运算的量,可以是常量,变量和返回值。
2、四则运算符号
C符号 | 意义 |
---|---|
+ | 加 |
- | 减 |
* | 乘 |
/ | 除法取整 |
% | 除法取余 |
3、C语言运算符优先级
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | -- |
() | 圆括号 | (表达式)/函数名(形参表) | -- | ||
. | 成员选择(对象) | 对象.成员名 | -- | ||
-> | 成员选择(指针) | 对象指针->成员名 | -- | ||
| |||||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
~ | 按位取反运算符 | ~表达式 | |||
++ | 自增运算符 | ++变量名/变量名++ | |||
-- | 自减运算符 | --变量名/变量名-- | |||
* | 取值运算符 | *指针变量 | |||
& | 取地址运算符 | &变量名 | |||
! | 逻辑非运算符 | !表达式 | |||
(类型) | 强制类型转换 | (数据类型)表达式 | -- | ||
sizeof | 长度运算符 | sizeof(表达式) | -- | ||
| |||||
3 | / | 除 | 表达式/表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | |||
% | 余数(取模) | 整型表达式%整型表达式 | |||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | |||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | |||
| |||||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | |||
< | 小于 | 表达式<表达式 | |||
<= | 小于等于 | 表达式<=表达式 | |||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | |||
| |||||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
| |||||
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
| |||||
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | -- |
/= | 除后赋值 | 变量/=表达式 | -- | ||
*= | 乘后赋值 | 变量*=表达式 | -- | ||
%= | 取模后赋值 | 变量%=表达式 | -- | ||
+= | 加后赋值 | 变量+=表达式 | -- | ||
-= | 减后赋值 | 变量-=表达式 | -- | ||
<<= | 左移后赋值 | 变量<<=表达式 | -- | ||
>>= | 右移后赋值 | 变量>>=表达式 | -- | ||
&= | 按位与后赋值 | 变量&=表达式 | -- | ||
^= | 按位异或后赋值 | 变量^=表达式 | -- | ||
|= | 按位或后赋值 | 变量|=表达式 | -- | ||
| |||||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 | -- |
说明:
同一优先级的运算符,运算次序由结合方向所决定。
简单记就是:! > 算术运算符 > 关系运算符 > && > || > 赋值运算符
3.判断
1、判断的格式
if(条件成立) {... } else {... }
if 和else 的后面都可以不跟大括号,不跟大括号的时候只有后面紧跟的一句有效。
2、判断的条件
计算两个值之间的关系叫做关系运算
运算符 | 含义 |
---|---|
= = | 相等 |
!= | 不等于 |
> | 大于 |
> = | 大于或等于 |
< | 小于 |
< = | 小于或等于 |
3、关系运算的结果
当两个值的关系符合关系运算符的预期时,关系运算的结果为整数1,否则为整数0
4、关系运算的优先级
所有关系运算符的优先级比算术运算的低,但比赋值运算的高。
==和!=比其他的低,连续的关系运算是从左向右有进行的。
补充
注释//,连续多行的注释用/* ..*/
5、嵌套的判断
•当if的条件满足或者不满足的时候要执行的语句也 可以是一条if或if-else语句,这就是嵌套的if语句
• else总是和最近的那个if匹配 ,•缩进格式不能表示else的匹配
6、级联的if
if ( exp1 ) st1; else if ( exp2 ) st2; else st3;
7、if语句的常见错误
•忘了大括号(做好永远在if和else 后面加上大括号,即使后面只有一条语句)
•if 后面的分号(if 后面不要加分号)
•等号==和赋值=混淆(if 只要求里面的值是0或者非0)
8、多路分支 switch-case
switch ( 控制表达式 ) { case 常量: 语句 …… case 常量: 语句 …… default: 语句 …… }
• 控制表达式只能是整数型的结果
• 常量可以是常数,也可以是常数计算的表达式
• 根据表达式的结果,寻找匹配的case,并执行case后⾯面的 语句,一直到break为止
• 如果所有的case都不匹配,那 么就执行default后⾯面的语句; 如果没有default,那么就什么 都不做
break
• switch语句可以看作是⼀一种基于计算的 跳转,计算控制表达式的值后,程序会 跳转到相匹配的case(分⽀支标号)处。 分支标号只是说明switch内部位置的路标,在执行完分支中的最后一条语句后, 如果后面没有break,就会顺序执行到下面的case里去,直到遇到一个break,或 者switch结束为止。
4.循环
1、while循环
格式
while ( x > 0 ) { x /= 10 ; n++; }
流程图
• 如果我们把while翻译作“当”,那么一个 while循环的意思就是:当条件满足时,不断地重复循环体内的语句。
• 循环执行之前判断是否继续循环,所以有可能循环一次也没有被执行;
• 条件成立是循环继续的条件
2、do -while 循环
格式
do { <循环体语句> } while ( <循环条件> );
流程图
• do-while循环和while循环很像,区别是在循环体执行结束的时候才来判断条件。也就是说,无论如何,循环都会执行至少一 遍,然后再来判断条件。与while循环相同的是,条件满足时执行循环,条件不满足时结束循环。注意do-while 循环后面的while要加分号
3、循环的应用
• 猜数游戏
- 计算机随机想一个数,记在变量number里;
- 一个负责计次数的变量count初始化为0;
- 让用户输入一个数字a;
- count递增(加一);
- 判断a和number的大小关系,如果a大,就输出 “大”;如果a小就输出“小”;
- 如果a和number是不相等的(无论大还是小),程序转回到第3步;
- 否则,程序输出“猜中”和次数,然后结束
代码示例
#include <stdio.h> #include<stdlib.h> #include<time.h> int main() { srand(time(0)); int number=rand()%100+1; int count=0; int a=0; printf("我已经想好了一个1到100之间的数\n"); do { printf("请输入你猜的数\n"); scanf ("%d",&a); count++; if (a>number) { printf("大了\n"); } else if (a<number) { printf("小了\n"); } } while(a!=number); printf("你用了%d次猜对.\n",count); system("pause"); return 0; }
•求平均值
- 初始化变量sum和count的值为0
- 读入number;
- 如果number的值小于0,结束循环,输出sum/count,否则,count加1,number值加入sum,回到2步
代码示例
#include <stdio.h> #include<stdlib.h> int main() { int number=0; int count=0; int sum=0; printf("请输入数\n"); scanf ("%d",&number); while(number>0) { sum=sum+number; count++; scanf ("%d",&number); } printf("平均值是%f\n",1.0*sum/count); system("pause"); return 0; }
•整数逆序
- ⼀个整数是由1⾄多位数字组成的,如何分解出整数
的各个位上的数字,然后加以计算 - 对⼀个整数做%10的操作,就得到它的个位数;
- 对⼀个整数做/10的操作,就去掉了它的个位数;
- 然后再对2的结果做%10,就得到原来数的十位数了;
- 依此类推。
代码示例
#include <stdio.h> #include<stdlib.h> int main() { int number=0; int a=0 ; int ret=0; printf("请输入数\n"); scanf ("%d",&number); while(number>0) { a=number%10; ret=ret*10+a; number/=10; } printf("逆序后的数是%d\n",ret); system("pause"); return 0; }
5.循环控制
5.1第三种循环(for 循环)
for ( 初始动作; 条件; 每轮的动作 )
{ }
• for中的每⼀个表达式都是可以省略的
for (; 条件; ) == while ( 条件 )
for( int i=1; i<=n; i++) { fact*=i; }
等价于
int i=1; while (i<=n) { fact*=i; i++; }
• 如果有固定次数,⽤for
• 如果必须执行⼀次,⽤do_while
• 其他情况⽤while
5.2循环控制
break和continue辨析
break是跳出循环体
而continue 是跳过这轮循环剩下的语句进入下一轮循环。break和continue只对所在的那层的循环有效。
素数是只能被1和其自身整除的数,不包含1
goto最好只用于多重循环的跳出,其他情况不要用,因为其很容易破坏代码结构。
样例
#include <stdio.h> #include<stdlib.h> int main() { int x; int one,two,five; scanf("%d",&x); for (one=1; one<x*10; one++) { for(two=1; two<x*10/2; two++) { for(five=1; five<x*10/5; five++) { if(one+two*2+five*5==x*10) { printf("可以用%d个一角,%d个2角,%d个5角构成%d元\n",one,two,five,x); goto out;//找到一个符合条件即跳出 } } } } out: system("pause"); return 0; }
5.3循环的应用
1、求前n项和(1+++...+)
代码示例
#include <stdio.h> #include<stdlib.h> int main() { int n; scanf("%d",&n); double sum=0; double sign=1.0; int i; for ( i=1; i<=n; i++) { sum=sum+sign/i; sign=-sign;//控制奇偶变号 } printf("f(%d)=%f",n,sum); system("pause"); return 0; }
2、将一个非负整数分解逐个输出
代码示例
#include <stdio.h> #include<stdlib.h> #include <math.h> int main() { int x,n=0; scanf("%d",&x); int t=x; do { t/=10; n++; } while(t>0); //这个循环用来求这个数的位数 printf("位数是%d位\n",n); int num,x1; for (; n>0; n--) { num=pow(10,n-1); x1=x/num;//得到首位数 x%=num;//x变成去掉首位后剩余的数 printf("%d\n",x1);//输出首位数 } system("pause"); return 0; }
3、求最大公约数(用辗转相除法)
#include <stdio.h> #include<stdlib.h> int main() { int a,b; int t; scanf("%d %d",&a,&b); int origa =a; int origb =b; while(b!=0) { t=a%b; a=b; b=t; } printf("%d和%d的最大公约数是%d",origa,origb,a); system("pause"); return 0; }
6.数据类型
6.0几道编程练习题解析
1、求符合给定条件的整数集
给定不超过6的正整数A,考虑从A开始连续4个数字,输出所有由他们组成的所有无重复数字的3位数
输入格式
输入在一行中给出A
输出格式
输出满足条件的三位数,要求从小到大,每行 6个,整数间以空格隔开,行末不能有多余空格。
代码样例
#include <stdio.h> #include<stdlib.h> int main() { int a,b,c,d; int i=0; scanf("%d",&a); b=a; while(b<=a+3) { c=a; while(c<=a+3) { d=a; while(d<=a+3) { if (b!=c &&c!=d && d!=b) { i++; printf("%d",b*100+c*10+d); if (i%6==0) { printf("\n"); } else { printf(" "); } } d++; } c++; } b++; } system("pause"); return 0; }
2、求水仙花数,水仙花数指一个N位正整数,它的每个位上的数字的N次幂之和等于它本身。
输入格式:
输入在一行中给出正整数N(3=<N<=7)
输出格式:
按递增顺序输出所有水仙花数,每个数字占一行。
代码样例
#include <stdio.h> #include<stdlib.h> int power(int m, int n) { int i = 1; int result = 1; if (n == 0) { return result; } for (i = 1; i <= n; i++) { result *= m; } return result; } //计算乘方的函数 int panduan(int a,int n) { int a1=a; int n1=n; int b; int sum=0; do { b=a1%10; a1/=10; sum+=power(b,n1); n=n-1; }while(n!=0); if (sum==a) {return 1; } else {return 0; } } //用来判断是不是水仙花数,是的话输出1 int main() { int n; int first; scanf("%d",&n); first=power(10,n-1); while(first<first*10) { if (panduan(first,n)==1) printf("%d\n",first); first++; }//n位数从小到大遍历,是水仙花数就输出 system("pause"); return 0; }
3、输出九九乘法表
对于任意给定的正整数N(1<N<10),输出从11到NN的乘法口诀表
输入格式:输入在一行中给出正整数N
输出格式:输出下三角式部分口诀表,其中等号右边数字占四位,左对齐。
代码样例
#include <stdio.h> #include<stdlib.h> int main() { int n; int x,y; x=1; scanf ("%d",&n); while (x<=n) { y=1; while (y<=x) { printf ("%d*%d=%d",x,y,x*y); if (x*y>=10) { printf(" "); } else { printf(" "); } y=y+1; } printf("\n"); x=x+1; } system("pause"); return 0; }
4、统计区间内的素数并对它们求和
输入格式
输入在一行中给出正整数M和N
输出格式
在一行中顺序输出M和N区间内素数的个数以及它们的和,数字间以空格分隔
代码样例
#include <stdio.h> #include<stdlib.h> int panduan(int a) { int b; int i; int sushu=1; if (a<=2) { return 1; }//1,2都是素数,不用判断了 for ( i=2;i<a;i++) { b=a%i; if (b==0)//被整除但不是自身或1,说明不是素数 { sushu=0; break; } } //如果sushu=1,说明是素数,返回1,否则不是素数,返回0 if (sushu) { return 1; } else { return 0; } } int main() { int m,n; int k=0; int sum=0; scanf ("%d %d",&n,&m); for(;n<=m;n++) { if (panduan(n)==1) { k=k+1; sum+=n; printf("%d\n",n);//是素数的话可以输出看看 } } printf("%d %d",k,sum); system("pause"); return 0; }
补充一些问题
1.return用法,附带一个返回值,返回调用处,并结束函数。
2.本来想定义一个bool 函数,但发现C中没有bool函数,C++才有,作个提醒
3.遇到了个“不是所有的控件路径都有返回值”错误,查资料知道是因为分支语句可能没有return,带返回值的函数如果在函数尾没有return语句,则在程序运行到函数尾部时,自动将离函数尾部最近的return语句作为该函数的结尾return。详见这篇文章
不是所有的控件路径都有返回值
5.猜数游戏
输入格式:
输入第一行给出2个不超过100的正整数,分别是系统产生的随机数,以及猜测的最大次数N,随后每行给一个用户输入,直到出现负数为止。
输出格式:
在一行中输出每次猜测相应的结果,直到输出猜对的结果或“Game Over!”则结束。
代码样例
#include <stdio.h> #include<stdlib.h> int main() { int a,n,b; int i; scanf("%d %d",&a,&n); for (i=1;i<=n;i++) { scanf("%d",&b); if (b<0) { printf("Game Over\n"); break; } else if (b==a) { if (i==1) { printf("Bing Go"); } else if (i<=3) { printf("LUCK you!"); } else { printf("Good Guess!\n"); } break; } else if (b>a) { printf("大了\n"); } else { printf("小了\n"); } } if (i==n+1) { printf("Game Over"); }//判断是超次退出,还是猜对退出 system("pause"); return 0; }
6.约分最简分式
输入格式:
12/34
输出格式同上
代码样例:
#include <stdio.h> #include<stdlib.h> int main() { int a,b; int c=1; int a1,b1; scanf("%d/%d",&a,&b); a1=a; b1=b; while (c!=0) { c=a%b; a=b; b=c; } printf("%d/%d",a1/a,b1/a);//辗转相除法,求出最大公约数,然后约分 system("pause"); return 0; }
7.输入一个整数,输出每个数字对应的拼音,当整数为负数时,先输出一个fu.
输入格式:1234
输出格式:fu yi er san si。每个数字的拼音之间用空格分开,行末没有最后的空格。
代码样例:
#include <stdio.h> #include <stdlib.h> int main() { int a; scanf("%d",&a); if (a<0) { printf("fu "); a=-a; } //先把负的变成正的 int b=1; int c=a; while (c>9) { c/=10; b*=10; }//获得10的n-1次方 do { int d=a/b;//取整,获得首位数字 switch(d){ case 0:printf("ling");break; case 1:printf("yi");break; case 2:printf("er");break; case 3:printf("san");break; case 4:printf("si");break; case 5:printf("wu");break; case 6:printf("liu");break; case 7:printf("qi");break; case 8:printf("ba");break; case 9:printf("jiu");break; } if (b>9) { printf(" "); } a%=b;//取余,获得去掉第一位后的剩下位数 b/=10;//10的n-1次方递推/10 } while (b>0);//一直到最后一位结束循环 printf("\n"); system("pause"); return 0; }
8、输入两个整数a和n,求数列之和S=a+aa+aaa+...aaa..a(n个a).
代码样例
#include <stdio.h> #include <stdlib.h> int main() { int a,n; scanf("%d %d",&a,&n); int sum=0,t=0; for (int i=1;i<=n;i++) { t=10*t+a; sum=sum+t; } printf("%d\n",sum); system("pause"); return 0; }
6.1数据类型
C语言的变量必须在使用前定义,并且确定类型。
C语言的类型
•整数
char、short、int、long、(long long)
•浮点数
float、double、(long double)
•逻辑
(bool)
•指针
•自定义类型
(括号里的是C99类型)
类型名称:int、long、double
• 输⼊入输出时的格式化:%d、%ld、%lf
• 所表达的数的范围:char < short < int < float <
double
• 内存中所占据的⼤大⼩小:1个字节到16个字节
• 内存中的表达形式:二进制数(补码)、编码
• Size of是一个运算符,给出某个类型或变量在内存中所占的字节数
• 是静态运算符,它的结果在编译时刻就决定了
• 不要在sizeof的括号⾥里做运算,这些运算不会做的
整数
• char:1字节(8比特)
• short:2字节
• int:取决于编译器(CPU),通常的意义是“1个字”
• long:取决于编译器(CPU),通常的意义是“1个字”
• long long:8字节
计算机内部一切数都是二进制
补码意义就是拿补码和原码可以加出一个溢出的0
• 因为0 - 1 —> -1,所以,-1 =
• (1)00000000 - 00000001 —> 11111111
• 11111111被当作纯⼆二进制看待时,是255,被当作补码看待时是
-1
• 同理,对于-a,其补码就是0-a,实际是2n - a,n是这种类型的位数
数的范围
• 对于一个字节(8位),可以表达的是:
• 00000000 - 11111111
• 其中
• 00000000 —> 0
• 11111111 ~ 10000000 —> -1 ~ -128
• 00000001 ~ 01111111 —> 1 ~ 127
• char:1字节:-128 ~ 127
• short:2字节:-32768 ~ 32767
• int:取决于编译器(CPU),通常的意义是“1个字”
• long:4字节
• long long:8字节
unsigned
• 在整形类型前加上unsigned使得它们成为无符号的整数
• 内部的二进制表达没变,变的是如何看待它们
• 如何输出
• 11111111
• 对于char,是-1
• 对于unsigned char,是255
• 如果一个字面量常数想要表达自己是unsigned,可以在后面加u或U
• 255U
• 用l或L表示long(long)
• *unsigned的初衷并非扩展数能表达的范围,而是为了做纯二进制运算,主要是为了移位
整数越界
• 整数是以纯二进制方式进⾏行计算的,所以:
• 11111111 + 1 —> 100000000 —> 0
• 01111111 + 1 —> 10000000 —> -128
• 10000000 - 1 —> 01111111 —> 127
整数的输入输出
• 只有两种形式:int 或long long
• %d:int (字节数小于int 的都有%d)
• %u:unsigned
• %ld:long long
• %lu:unsigned long long
8进制和16进制
• 一个以0开始的数字字面量是8进制
• 一个以0x开始的数字字面量是16进制
• %o⽤用于8进制,%x用于16进制
浮点数
类型 | 字长 | 范围 | 有效数字 |
---|---|---|---|
float | 32 | ±(1.20x10^-38 ~3.40*10^38),0,±inf,nan | 7 |
double | 64 | ±(2.2*10^-308~ 1.79 *10^ 308),0,±inf,nan | 15 |
(有效数字表示只有几位有效的,后面的数字是不准确的 ) | |||
输入输出格式 | |||
类型 | scanf | printf | |
-- | -- | -- | |
float | %f | %f,%e | |
double | %lf | %f,%e |
输出精度
• 在%和f之间加上.n可以指定输出小数点后几位,这样
的输出是做4舍5入的
• printf输出inf表示超过范围的浮点数:±∞
• printf输出nan表示不存在的浮点数
• 带小数点的字面量是double而非float
• float需要用f或F后缀来表明身份
浮点数直接判断 f1 == f2 可能失败
一般用差的绝对值 fabs(f1-f2) < 1e-12,因为有效位数后面的数字是不准确的
字符
char是一种整数,也是一种特殊的类型:字符。这是因为:
• 用单引号表示的字符字面量:'a', '1'
• ''也是一个字符
•也可以用ASCII编码表示,ASCII编码在形式上是整数
• printf和scanf里用%c来输入输出字符
字符计算
• 一个字符加⼀一个数字得到ASCII码表中那个数之后的字符
• 两个字符的减,得到它们在表中的距离
大小写转换
• 字母在ASCII表中是顺序排列的
• 大写字母和小写字母是分开排列的,并不在一起
• ‘a’-‘A’可以得到两段之间的距离,于是
• a+’a’-‘A”可以把一个大写字母变成小写字母,而
• a+’A’-‘a’可以把一个小写字母变成大写字母
转义字符
反斜杠“ \”加上后面的一个字符组成一个特殊字符
制表位
• 每行的固定位置
• 一个\t使得输出从下一个制表位开始
• 用\t才能使得上下两行对齐
逻辑类型
• #include <stdbool.h>
• 之后就可以使⽤用bool和true、false
• bool实际上还是以int的手段实现的,所以可以当作int来计算
• 也只能当作int来输入输出
自动类型转换
• 当运算符的两边出现不一致的类型时,会自动转换成较大类型
• 大的意思是能表达的数的范围更大
• char —> short —> int —> long —> long long
• int —> float —> double
• 对于printf,任何小于int的类型会被转换成int;float会被转成double
• 但是scanf不会,scanf 要明确知道变量的大小,要输short,需要%hd
• 强制类型转换的优先级高于四则运算
6.2其他运算(逻辑,条件,逗号)


短路
• 逻辑运算是自左向右进行的,如果左边的结果已经能够决定结果了,就不会做右边的计算
• a== 6 && b== 1
• a==6 && b+=1
• 对于&&,左边是false时就不做右边了
• 对于||,左边是true时就不做右边了
不要把赋值,包括复合赋值组合进表达式!

• 条件运算符的优先级高于赋值运算符,但是低于其他运算符
逗号运算符
• 逗号用来连接两个表达式,并以其右边的表达式的值作为它的结果。逗号的优先级是所有的运算符中最低的,所以它两边的表达式会先计算;逗号的组合关系是自左向右,所以左边的表达式会先计算,而右边的表达式的值就留下来作为逗号运算的结果。
主要在for中使用,没啥大用
•for ( i=0, j=10; i<j; i++, j- - ) ……
7.函数
7.1函数的定义和使用
1、什么是函数
• 函数是一块代码,接收零个或多个参数,
做一件事情,并返回零个或一个值
• 可以先想像成数学中的函数:
• y = f(x)
2、函数的使用
• return停止函数的执行,并送回一个值
• return有两种用法
直接 return;
return+ 表达式;
• 一个函数里可以出现多个return语句
•函数的返回值可以赋值给变量,可以再传递给函数,甚至可以丢弃,有时候要的是副作用。
7.2函数的参量和变量
1、函数原型


如果把调用的函数放到下面了,可能会报错
2、参数传递
• 调用函数时给的值与参数的类型不匹配是C语言传统上最大的漏洞
• 编译器总是悄悄替你把类型转换好,但是这很可能不是你所期望的
• 后续的语言,C++/Java在这方面很严格
★C语言在调用函数时,永远只能传值给函数
本地变量
• 函数的每次运行,就产生了一个独立的变量空间,在这个空间中的变量,是函数的这次运行所独有的,称作本地变量
• 定义在函数内部的变量就是本地变量
• 参数也是本地变量
变量的生存期和作用域
• 生存期:什么时候这个变量开始出现了,到什么时候它消亡了
• 作用域:在(代码的)什么范围内可以访问这个变量(这个变量可以起作用)
• 对于本地变量,这两个问题的答案是统一的:大括号内——块
其他细节
没有参数时
• void f(void)还是• void f();
• 在传统C中,void f()表示f函数的参数表未知,并不表示没有参数;void f(void)明确表示没有任何参数。
逗号运算符
• 调用函数时的圆括号里的逗号是标点符号,不是运算符
• f(a,b)
• f((a,b))再加一个括号就表示逗号运算符了。
• C语言不允许函数嵌套定义,但是可以在一个函数里面放另一个函数的声明。
• int main ()也是一个函数,可以写成 int main(void),表示没有任何参数。
• return 0是有意义的。
8.数组
8.1数组的定义和使用
定义数组
• <类型> 变量名称[元素数量];
• int grades[100];
• double weight[20];
• 元素数量必须是整数
• C99之前:元素数量必须是编译时刻确定的字面量
数组
• 是一种容器(放东西的东西),特点是:
• 其中所有的元素具有相同的数据类型;
• 一旦创建,不能改变大小
• *(数组中的元素在内存中是连续依次排列的)
数组的单元
• 数组的每个单元就是数组类型的一个变量
• 使用数组时放在[]中的数字叫做下标或索引,下标从0开始计数:
• grades[0]
• grades[99]
• average[5]
有效的下标范围
• 编译器和运行环境都不会检查数组下标是否越界,无论是对数组单元做读还是写
• 一旦程序运行,越界的数组访问可能造成问题,导致程序崩溃
• segmentation fault!
• 但是也可能运气好,没造成严重的后果
• 所以这是程序员的责任来保证程序只使用有效的下标值:[0,数组的大小-1]
长度为0的数组?
• int a[0];
• 可以存在,但是无用。
数组的例子:投票统计
• 写一个程序,输入数量不确定的[0,9]范围内的整数,统计每一种数字出现的次数,输入-1表示结束。
8.2数组运算
8.2-1 数组的运算


数组的大小
• sizeof给出整个数组所占据的内容的大小,单位是字节
• sizeof(a[0])给出数组中单个元素的大小,于是相除就得到了数组的单元个数
• 通常都是使用for循环,让循环变量i从0到<数组的长度,这样循环体内最大的 i 正好是数组最大的有效下标
常见错误是:
• 循环结束条件是<=数组长度,或;
• 离开循环后,继续用 i 的值来做数组元素的下标!
数组作为函数参数时,往往必须再用另一个参数来传入数组的大小
数组作为函数的参数时:
• 不能在[ ]中给出数组的大小
• 不能再利用sizeof来计算数组的元素个数!
8.2-2数组的例子:素数
例1.判断是否能被已知的小于x的素数整除
代码示例
#include <stdio.h> #include <stdlib.h> int isprime(int x, int knownprimes[],int numberofknownpimes); int main(void) { const int number=10; int prime[number]={2}; int count =1; int i=3; while (count<number) { if (isprime(i,prime,count)) { prime[count++]=i; } { printf("i=%d \tcnt=%d\t",i,count); int a; for (a=0;a<number;a++) { printf("%d\t",prime[a]); } printf("\n"); } i++; } for (i=0;i<number;i++) { printf("%d",prime[i]); if ((i+1)%5) { printf("\t"); } else printf("\n"); } system("pause"); return 0; } int isprime(int x, int knownprimes[],int numberofknownpimes) { int ret=1; int i; for (i=0;i<numberofknownpimes;i++) { if (x%knownprimes[i]==0) { ret=0; break; } } return ret; }
运行结果
例2,构造素数表
代码示例
#include <stdio.h> #include <stdlib.h> int main(void) { const int maxnumber=25; int isprime[maxnumber]; int i; int x; for (i=0;i<maxnumber;i++) { isprime[i]=1; } printf("\t"); for (i=2;i<maxnumber;i++) { printf("%d\t",i); } printf("\n"); for (x=2;x<maxnumber;x++) { if (isprime[x]) { for (i=2;i*x<maxnumber;i++) { isprime[i*x]=0; } } printf("%d\t",x); for (i=2;i<maxnumber;i++) { printf("%d\t",isprime[i]); } printf("\n"); } for (i=2;i<maxnumber;i++) { if (isprime[i]) { printf("%d\t",i); } } printf("\n"); system("pause"); return 0; }
8.2-3 二维数组
• int a[3][5];
• 通常理解为a是一个3行5列的矩阵
• a[i][j]是一个int
• 表示第i行第j列上的单元

代码示例
代码有些问题
#include <stdio.h> #include <stdlib.h> int main(void) { const int size=3; int board[size][size]; int i,j; int numofX; int numofo; int result=-1; //读入矩阵 for (i=0;i<size;i++) { for (j=0;j<size;j++) { scanf("%d",&board[i][j]); } } //检查行 for (i=0;i<size && result==-1;i++) { numofo=numofX=0; for (j=0;j<size;j++) { if (board[i][j]==1) { numofX++; } else { numofo++; } } if (numofo==size) { result=0; printf("O赢了\n"); break; } else if(numofX==size) { result=1; printf("X赢了\n"); break; } } //检查列 if (result==-1) { for (j=0;j<size&&result==-1;j++) { numofo=numofX=0; for (i=0;i<size;i++) { if (board[i][j]==1) { numofX++; } else { numofo++; } } if (numofo==size) { result=0; printf("O赢了\n"); break; } else if(numofX==size) { result=1; printf("X赢了\n"); break; } } } //检查对角线 numofo=numofX =0; for (i=0;i<size;i++) { if (board[i][i]==1) { numofX++; } else { numofo++; } } if (numofo==size) { result=0; printf("O赢了\n"); return 0; } else if (numofX==size) { result=1; printf("X赢了\n"); return 0; } numofo=numofX =0; for (i=0;i<size;i++) { if (board[i][size-i-1]==1) { numofX++; } else { numofo++; } } if (numofo==size) { result=0; printf("O赢了\n"); return 0; } else if (numofX==size) { result=1; printf("X赢了\n"); return 0; } if (result==-1) { printf("没人赢"); } system("pause"); return 0; }
9.指针
9.1指针的定义和使用
1、取地址运算符&
• scanf(“%d”, &i);里的&
• 获得变量的地址,它的操作数必须是变量
• int i; printf(“%x”,&i);
• 地址的大小是否与int相同取决于编译器
• int i; printf(“%p”,&i);
•&后面跟的一定是变量,不能对没有地址的东西取地址
如 &(a+b)、 &(a++)、&(++a)
2、指针
指针就是保存地址的变量。
int i; int* p = &i; int* p,q; int *p,q;
无论 *号是靠近 int 还是靠近 p都只表示p是一个指针,q不是。若想让q也是一个指针,那么在q前面也加一个 * 号
• 指针变量的值是内存的地址
• 普通变量的值是实际的值
• 指针变量的值是具有实际值的变量的地址
访问那个地址上的变量 *
• *是一个单目运算符,用来访问指针的值所表示的地址上的变量
• 可以做右值也可以做左值
• int k = *p;
• *p = k+1;
指针最常见的错误
• 定义了指针变量,还没有指向任何变量,就开始使用指针,未初始化之前使用 *p 是没有任何意义的。
3、指针与数组

数组参数
• 以下四种函数原型是等价的:
• int sum(int *ar, int n); • int sum(int *, int); • int sum(int ar[], int n); • int sum(int [], int);

4、指针与const (C99 Only!)
指针是const
• 表示一旦得到了某个变量的地址,不能再指向其他变量
int * const q = &i; //q 是 const *q = 26; // OK q++; // ERROR
所指是const
• 表示不能通过这个指针去修改那个变量(并不能使得那个变量成为const)
const int *p = &i; *p = 26; // ERROR! (*p) 是 const i = 26; //OK p = &j; //OK
判断哪个被const了的标志是const在*的前面还是后面,const在 * 号前面是所指不能修改,在 * 后面是指针不能修改
const数组
• const int a[] = {1,2,3,4,5,6,};
• 数组变量已经是const的指针了,这里的const表明数组的每个单元都是const int
• 所以必须通过初始化进行赋值
• 因为把数组传入函数时传递的是地址,所以那个函数内部可以修改数组的值
• 为了保护数组不被函数破坏,可以设置参数为const
• int sum(const int a[], int length);
9.2指针的运算
1、指针运算
• 给一个指针加1表示要让指针指向下一个变量
int a[10]; int *p = a; *(p+1) —> a[1]
• 如果指针不是指向一片连续分配的空间,如数组,则这种运算没有意义
• 这些算术运算可以对指针做:
• 给指针加、减⼀一个整数(+, +=, -, -=)
• 递增递减(++/—)
• 两个指针相减
*p++
• 取出p所指的那个数据来,完事之后顺便把p移到下一个位置去
• *的优先级虽然高,但是没有++高
• 常用于数组类的连续空间操作
• 在某些CPU上,这可以直接被翻译成一条汇编指令
指针比较
• <, <=, ==, >, >=, != 都可以对指针做
• 比较它们在内存中的地址
• 数组中的单元的地址肯定是线性递增的
0地址
• 当然你的内存中有0地址,但是0地址通常是个不能随便碰的地址
• 所以你的指针不应该具有0值
• 因此可以用0地址来表示特殊的事情:
• 返回的指针是无效的
• 指针没有被真正初始化(先初始化为0)
• NULL是一个预定定义的符号,表示0地址
• 有的编译器不愿意你用0来表示0地址
指针的类型转换
• void* 表示不知道指向什么东西的指针
• 计算时与char*相同(但不相通)
• 指针也可以转换类型
• int * p = &i; void * q = (void * )p;
• 这并没有改变p所指的变量的类型,而是让后人用不同的眼光通过p看它所指的变量
• 我不再当你是int啦,我认为你就是个void!
2、动态内存分配
//malloc include <stdlib.h> void* malloc(size_t size);
• 向malloc申请的空间的大小是以字节为单位的
• 返回的结果是void,需要类型转换为自己需要的类型
• (int)malloc(n*sizeof(int))
• 如果申请失败则返回0,或者叫做NULL
• 你的系统能给你多大的空间?
free()
• 把申请得来的空间还给“系统”
• 申请过的空间,最终都应该要还
• 出来混的,迟早都是要还的
• 只能还申请来的空间的首地址
• free(0)?
常见问题
• 申请了没free—>长时间运行内存逐渐下降
• 新手:忘了
• 老手:找不到合适的free的时机
• free过了再free
• 地址变过了,直接去free
10.字符串
10.1字符串定义
字符串
• 以0(整数0)结尾的一串字符
• 0或’\0’是一样的,但是和’0’不同
• 0标志字符串的结束,但它不是字符串的一部分
• 计算字符串长度的时候不包含这个0
• 字符串以数组的形式存在,以数组或指针的形式访问
• 更多的是以指针的形式
• string.h 里有很多处理字符串的函数
字符串变量
char *str = “Hello”; char word[] = “Hello”; char line[10] = “Hello”;
字符串常量
• “Hello”
• ″Hello″ 会被编译器变成一个字符数组放在某处,这个数组的长度是6,结尾还有表示结束的0
• 两个相邻的字符串常量会被自动连接起来
• 行末的\表示下一行还是这个字符串常量
• C语言的字符串是以字符数组的形态存在的
• 不能用运算符对字符串做运算
• 通过数组的方式可以遍历字符串
• 唯一特殊的地方是字符串字面量可以用来初始化字符数组
• 以及标准库提供了一系列字符串函数
char* s = "Hello, world!";
• s 是一个指针,初始化为指向一个字符串常量
• 由于这个常量所在的地方,所以实际上s是 const
char* s ,但是由于历史的原因,编译器接受不带const的写法
• 但是试图对s所指的字符串做写入会导致严重的后果
• 如果需要修改字符串,应该用数组:
char s[] = "Hello, world!";

char * 是字符串?
• 字符串可以表达为char * 的形式
• char * 不一定是字符串
• 本意是指向字符的指针,可能指向的是字符的数组(就像int *一样)
• 只有它所指的字符数组有结尾的0,才能说它所指的是字符串
字符串赋值
char *t = “title”; char *s; s = t;
• 并没有产生新的字符串,只是让指针s指向了t所指的字符串,对s的任何操作就是对t做的
字符串输入输出
char string[8]; scanf(“%s”, string); printf(“%s”, string);
• scanf读入一个单词(到空格、tab或回车为止)
• scanf是不安全的,因为不知道要读入的内容的长度
安全的输入
char string[8]; scanf(“%7s”, string);
• 在%和s之间的数字表示最多允许读入的字符的数量,这个数字应该比数组的大小小1
常见错误
char * string; scanf(“%s”, string);
• 以为char*是字符串类型,定义了一个字符串类型的变量string就可以直接使用了,也就是说使用前要初始化。
• 由于没有对string初始化为0,所以不一定每次运行都出错
空字符串
• char buffer[100]=””;
• 这是一个空的字符串,buffer[0] == ‘\0’
• char buffer[] = “”;
• 这个数组的长度只有1!
10.2字符串函数
putchar 字符输出
• int putchar(int c);
• 向标准输出写一个字符
• 返回写了几个字符,EOF(-1)表示写失败
getchar 字符输入
• int getchar(void);
• 从标准输入读入一个字符
• 返回类型是int是为了返回EOF(-1)
标准库中的字符串函数
string.h
• strlen
• strcmp
• strcpy
• strcat
• strchr
• strstr
strlen
size_t strlen(const char *s);
• 返回s的字符串长度(不包括结尾的0)
strcmp
int strcmp(const char *str1, const char *str2);
strcmp() 会根据 ASCII 编码依次比较 str1 和 str2 的每一个字符,直到出现不同的字符,或者到达字符串末尾(遇见\0)。
• 比较两个字符串,返回:
• 0:s1==s2
• >0:s1>s2
• <0:s1<s2
strcpy
char * strcpy(char *restrict dst, const char *restrict src);
• 把src的字符串拷贝到dst
• restrict表明src和dst不重叠(C99)
复制一个字符串
char *dst = (char*)malloc(strlen(src)+1); strcpy(dst, src);
strcat
char * strcat(char *restrict s1, const char *restrict s2);
• 把s2拷贝到s1的后面,接成一个长的字符串
• 返回s1
• s1必须具有足够的空间
strcpy和strcat都可能出现安全问题
• 如果目的地没有足够的空间?
安全版本
char * strncpy(char *restrict dst, const char *restrict src, size_t n); char * strncat(char *restrict s1, const char *restrict s2, size_t n); int strncmp(const char *s1, const char *s2, size_t n);
字符串中找字符
char * strchr(const char *s, int c); char * strrchr(const char *s, int c);
• 返回NULL表示没有找到
字符串中找字符串
char * strstr(const char *s1, const char *s2); char * strcasestr(const char *s1, const char *s2);
11.结构类型
11.1枚举

• 枚举量可以作为值
• 枚举类型可以跟上enum作为类型
• 但是实际上是以整数来做内部计算和外部输入输出的
#include <stdio.h> enum COLOR { RED, YELLOW, GREEN, NumCOLORS }; int main(int argc, char const *argv[]) { int color = -1; char *ColorNames[NumCOLORS] = { "red", "yellow","green", }; char *colorName = NULL; printf("输入你喜欢的颜色的代码: "); scanf("%d", &color); if (color >= 0 && color < NumCOLORS) { colorName = ColorNames[color]; } else { colorName = "unknown"; } printf("你喜欢的颜色是%s\n",colorName); return 0; }
枚举量
• 声明枚举量的时候可以指定值
• enum COLOR { RED=1, YELLOW, GREEN = 5};
#include <stdio.h> enum COLOR { RED = 1, YELLOW, GREEN = 5, NumCOLORS }; int main(int argc, char const *argv[]){ printf("code for GREEN is %d\n", NumCOLORS); return 0; }
• 虽然枚举类型可以当作类型使用,但是实际上很(bu)少(hao)用
• 如果有意义上排比的名字,用枚举比const int⽅便
• 枚举⽐比宏(macro)好,因为枚举有int类型
11.2结构
●和本地变量一样,在函数内部声明的结构类型只能在函数内部使用
●所以通常在函数外部声明结构类型,这样就可以被多个函数所使用了
对于第一和第三种形式,都声明了结构point。但是第二种形式没有声明point,只是定义了两个变量
结构的初始化
#include <stdio.h> struct date { int month; int day; int year; }; int main(int argc,char const *argv[]) {struct date today = { 07 ,31,2014 }; struct date thismonth = { .month = 7,.year = 2014 }; printf("Today's date is %i-%i-%i. \n", today.year, today.month, today.day); printf("This month is %i- %i- %i. \n", thismonth.year, thismonth.month, thismonth.day); return 0; }
结构成员
●结构和数组有点像
●数组用 [ ] 运算符和下标访问其成员
●a[0]= I0;
●结构用 . 运算符和名字访问其成员
结构运算
●要访问整个结构,直接用结构变量的名字
●对于整个结构,可以做赋值、取地址,也可以传递给函数参数
●pl = (struct point){5, I0}; //相当于pl.x= 5;pl.y= 10;
●pI =p2; // 相当于pl.x=p2.x;pl.y= p2.y;
数组无法做这两种运算
复合字面量
●today = (struct date) {9,25,2004};
●today = (struct date) {.month=9, .day=25,.year=2004};
结构指针
●和数组不同,结构变量的名字并不是结构变量的地址,必须使用&运算符
●struct date *pDate = &today ;
结构作为函数参数
int number0fDays (struct date d)
●整个结构可以作为参数的值传入函数
●这时候是在函数内新建一个结构变量,并复制调用者的结构的值
●也可以返回一个结构
●这与数组完全不同
结构指针作为函数参数
//指向结构的指针 struct date { int month; int day; int year; } myday; struct date *p = &myday; (*p).month = 12; p->month = 12;
用->表示指针所指的结构变量中的成员

11.3联合


typedef int Length; //Length就等价于int类型 typedef *char[10] Strings; // Strings是10个字符串的数组的类型 typedef struct node { int data; struct node *next; } aNode; //或 typedef struct node aNode; //这样用aNode就可以代替 struct node;
联合
联合和结构非常相似,
●存储
●所有的成员共享一个存储空间
●同一时间只有一个成员是有效的
●union的大小是 其最大的成员
本文作者:zhxu2000
本文链接:https://www.cnblogs.com/zhxu2000/p/16022088.html
版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步