1.类型转换
1.1 隐式类型转换
在两个不同类型数据进行运算时,会把低精度类型的数据转为与高精度类型一致的数据类型然后计算,然后再根据赋值的需要把计算结果转回去
1.2 强制类型转换
可以将某种类型的数据转换为想要的精度,一般int、double、float可以相互转,但是可能会损失精度。
- 注意,如果想要将字符串强制转为int类型将会报编译错误
2. 输入输出控制
2.1 输出控制:printf
格式控制符%d的数量可以不与需要输出的字符数量对应,不会报错,但是会造成输出与预期不符的情况。
如果没有格式控制符,只有想要输出的变量,将会报编译错误
2.2 输入控制:scanf
输入存入的数据需要提供该数据的地址,形如&a(变量a的地址)
有两种典型的错误:
- 未提供地址:如下所示,将会报编译错误
scanf("%d",a);
- 提供地址数少于输入:如下所示,将会报运行时错误
scanf("%d %d",&a);//输入两个数据
2.3 输出格式控制(C风格)
指定解读方式
- %d: 解读为有符号十进制(decimal)整数
- %f:解读为单精度浮点数(float)
- %u:解读为无符号(unsigned)十进制整数
- %c:解读为单个字符(character)
例子:
int a =65;
printf("%d %c %f",a,a,a);
//输出结果为:65 A 0.000000
指定数据格式
控制宽度
- %md:指定输出宽度为m,默认右对齐,如果不足则补空格
控制小数位数
- %.mf:指定暴力小数点后m位,四舍五入
同时控制宽度和小数位数
- %n.mf:指定宽度为n(默认右对齐),并且保留小数点后m位,注意小数点也占一个宽度,不足宽补空格
指定输出整数的进制
- %0x:将数据理解为整数,并且输出16进制表示,注意是零和x(x小写则16进制字母小写)(X大写则16进制字符大写)
使用指定字符控制宽度
- %0md:指定整数宽度为m,宽度不足用0补齐
需要输出%
连续使用两个%,前一个%作为转义符号
输入格式控制
scanf("Please input:%d",&a);
- 上述代码指明了程序期望的用户输入格式
- 如果用户按照Please input:1的格式输入,则可以正常运行
- 如果直接输入1,不会报错,但是读取的输入会与预期不符合
一些例子
3.宏定义与不变量
3.1 宏(文本批量替换功能)
- 在编程领域,“宏”就是指通过预设,对指定的文本内容进行替换
- c语言中,通过#define预编译指令定义一个宏
- 如下宏定义代码,在预编译阶段(即编译前),把源码中所有的PI改成对应的数字
#define PI 3.141592679545215498795461648794856213245978945123126748974653126857984653268757712954728768654554
- 宏定义不止可以定义常量,也可以带参数定义:
- 需要注意宏定义是简单机械的替换,注意下面这个例子,为了避免这样的问题,可以在宏定义时对计算的值加上括号
3.2 不变量
- 不变量是加限制的变量,即不可通过赋值符号进行改变(i.e. 不可作为等号左值),在普通变量定义前加上const即可定义为不变量(注意是加在最前面)
- 注意,试图通过等号为不变量赋值,将会报编译错误
- 事实上,不变量只是限定为不能通过赋值符号赋值,不可以作为左值,但是可以作为右值参与运算,如果需要修改值,可以通过指针+间接引用改变绕过限制,但是不建议这么做
常见的错误理解:(A、B、D会编译错误,C绕过限制不报错,但是不推荐这样做)
3.3 宏定义与不变量辨析
- 宏定义是预编译指令,本质是文本的替换,支持复杂的带参数的文本模式替换
- 不变量是语句,本质是对变量的修饰,限定其不能通过赋值号赋值
#define定义的常量和const限定的不变量的区别
4.技术知识
4.1 转义字符
- ‘\n’、‘\0’、‘\t’、‘’‘、’"‘、’\'等使用反斜杠
4.2 “溢出”
- 例如unsigned int加到最大值,再加一会溢出,显示出来的值是0
- 溢出其实是,最大值再加1回到最小值,最小值再减1回到最大值
4.3 小数
- 整数除法的本质是做隐式类型转换:int=>float=>int
- float=>int的类型转换是截除小数的,会损失精度,不会进行四舍五入
- printf使用的格式控制用于指定对数据的理解和呈现方式,不是类型转换
- %.mf指将数据理解为浮点数,并且呈现m为,涉及对数据的理解,会进行四舍五入
5. 函数
5.1 函数定义——做饼
明确函数 :
- 接收的参数
- 返回值类型
- 具体操作
函数的定义主要包括:指明返回值、明确参数列表、编写函数逻辑三部分
5.2 函数声明——画饼
明确函数:
- 接收的参数
- 返回值的类型
函数定义是函数的实际执行部分(工人师傅),函数声明就是明确接收什么返回什么(名片),不涉及细节
5.3 函数调用与传参
调用时找到声明即可。
- 如果调用时看不到声明,则报编译错误
- 如果找到声明找不到定义,则报链接错误
5.4 函数返回值
- 无论再函数内部何处,return语句的执行将立即结束函数并提交返回值
- 函数结束后,再调用位置原地替换为返回值
- 无需提供返回值用void
5.5 注意点
- 函数声明可以放在另一个函数的定义中,但函数定义不可以,不同函数的定义需要隔离开来
- 一个函数只能返回一个值
5.6 实参与形参
int sum(int a,int b){return a+b;
}
int main(){int m = 1,n=2;int res = sum(m,n);return 0;
}
上述代码中:
- a,b是形式参数
- m,n是实际参数
注意:
- 函数声明的形参只是一个说明,其实没有变量名都可以,但是函数定义的形参真实存在
- 函数传参其实是将实际参数的值copy给形式参数作为初始化,不变量也允许作为形参,请见下面的例题:
6. 分支结构branch
6.1 if-else-else if语句
判断闰年的if语句
闰年:
- (1)四年一闰百年不闰:即如果year能够被4整除,但是不能被100整除,则year是闰年。
- (2)每四百年再一闰:如果year能够被400整除,则year是闰年。
int a;
scanf("%d",&a);
if(a%4){printf("NO");
}else{if(a%100){ printf("YES");}else if(a%400){printf("NO");}else{printf("YES");}
}
6.2 条件表达式
- 比较运算符:==、>、<、>=、<=、!=
- 条件表达式:通过比较运算符对左右操作数进行运算
- 逻辑运算符:&&、||、!
使用逻辑表达式表达复杂的组合判断逻辑,仍然是闰年问题:
if(a%4!=0||a%100==0&&a%400!=0){printf("NO");
}else{printf("YES");
}
运算符优先级
- 算数运算符>!>比较运算符>&&>||
- 即:先运算,再比较,最后组合
逻辑表达式的短路
- 如果a=1,那么a||b一定是1,不用考虑b,即b被短路
- 如果a=0,那么a&&b一定是0,不用考虑b,即b被短路
7. 循环结构
循环三要素:起点、终点、步长
7.1 while
- while的执行:出口判断->执行一次循环->出口判断…
- do-while的执行:执行一次循环->出口判断->执行一次循环…
为了避免将形如a==0的判断语句写成a=0造成不执行的情况,可以写成0==a,因为0=a会报编译错误
7.2 for
- for适用于迭代:能够不重不漏地访问范围内所有元素
for (int i = 0; i < 1; ++i) {printf("%d",a++);}
-
for执行的顺序:
-
- int i =0;
-
- i<1;
-
- printf(“%d”,a++);
-
- ++i
-
- 回到2
-
-
for括号内的两个分号不能少
7.3 循环控制
- break:提前终止当前整个循环
- continue:跳过本轮循环剩下的执行内容,直接开始下一次
8.数组
8.1 数组组成
- 基础数据类型:int、float类型的数组
- 自定义数据类型:struct、union类型的数组
8.2 数组初始化
- 通过列表初始化:
//全部初始化
int a[7]={0,1,2,3,4,5,6};
//部分初始化
int b[7]={0,1,2};//未初始化默认为0,故b[7]={0,1,2,0,0,0,0};
//不确定个数初始化
int c[]={0,1,2};
int c_size = sizeof(c)/4;//c_size=3
//注意sizeof计算的是字节数
- 注意,只有数组定义时才能批量初始化,定义完成后只能通过索引 访问
8.3 数组长度
- C语言中的数组特指定长数组
- 因此定义数组元素数量必须使用常量或不变量
- 准确来说,是编译期能直接确定数值的量
- 已经定义好长度的数组,访问超过长度范围的索引值,会报数组越界(运行时错误)
获取已定义数组的元素数量
int length = sizeof(a)/sizeof(int);
8.4 数组遍历
形如下:
int a[n];
for(int i=0;i<n;i++){a[i]=i;
}
8.5 多维数组
- 实际上是按顺序排列的
9. 结构体
9.1 声明结构体
struct item{ //1.声明结构体int id;
};
struct item a; //2.定义结构体变量a.id = 10; //3.访问结构体变量的成员
- 声明:让编译器知道存在这么一个东西
- 定义:让编译器实际生成一个东西(分配空间)
声明与定义的两种形式
- 先声明后定义
- 声明并定义
typedef关键字
typedef unsigned long long uint64;uint64 a;//= unsigned long long a;
- typedef不是宏定义替换
- typedef可以给类型取别名
注意与结构体声明并定义的区别!!
9.2 使用结构体
初始化结构体内部的成员变量
typedef struct student{char name[20];unsigned long long id;int age;float score;
}S;
//不得用等号,要用冒号
S s1 = {"Mike",2222,age:19,score:100.0f};//如果按照顺序初始化,可以不标注所赋值变量名
结构体大小
- 使用sizeof获取结构体变量所占内存大小,原则上来说,大小等于其所有数据成员大小之和
- 例外情况:内存对齐:通常是4字节对齐,即如果定义结构体大小不是4字节的整数倍那么将向上对4取整
9.3 联合体union
联合体存在的意义
为什么有了结构体还要有联合体?
- 场景1:各个成员互斥,同一时间只有一项生效
- 场景2:同一个数据可以合起来解读,也可以分开多部分解读
联合体在内存资源紧张的平台(如嵌入式)广泛使用
计算内存空间占用问题
- union没有内存空间对齐的要求,内存空间占用即为最大成员的内存空间占用
- struct有内存空间对齐的要求,内存空间占用是所有成员占用空间总和,还要注意对4向上取整
联合体各个数据成员共享空间,对任意成员的修改都会反映到所有数据上
9.4 枚举类型enum
enum day {a=1,b,c,d,e,f,g};enum day dd = b;printf("%d %d",dd,e);//输出:2 5
参考上述代码可知,enum相当于定义了一系列变量,默认按照递增顺序给未赋值的变量赋值。