C语言要点细细梳理(上)

1.类型转换

1.1 隐式类型转换

在两个不同类型数据进行运算时,会把低精度类型的数据转为与高精度类型一致的数据类型然后计算,然后再根据赋值的需要把计算结果转回去

1.2 强制类型转换

可以将某种类型的数据转换为想要的精度,一般int、double、float可以相互转,但是可能会损失精度。

  • 注意,如果想要将字符串强制转为int类型将会报编译错误

2. 输入输出控制

2.1 输出控制:printf

格式控制符%d的数量可以不与需要输出的字符数量对应,不会报错,但是会造成输出与预期不符的情况。

如果没有格式控制符,只有想要输出的变量,将会报编译错误

2.2 输入控制:scanf

输入存入的数据需要提供该数据的地址,形如&a(变量a的地址)

有两种典型的错误:

  • 未提供地址:如下所示,将会报编译错误
c 复制代码
scanf("%d",a);
  • 提供地址数少于输入:如下所示,将会报运行时错误
c 复制代码
scanf("%d %d",&a);//输入两个数据

2.3 输出格式控制(C风格)

指定解读方式

  • %d: 解读为有符号十进制(decimal)整数
  • %f:解读为单精度浮点数(float)
  • %u:解读为无符号(unsigned)十进制整数
  • %c:解读为单个字符(character)

例子:

c 复制代码
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补齐
需要输出%

连续使用两个%,前一个%作为转义符号

输入格式控制
c 复制代码
scanf("Please input:%d",&a);
  • 上述代码指明了程序期望的用户输入格式
  • 如果用户按照Please input:1的格式输入,则可以正常运行
  • 如果直接输入1,不会报错,但是读取的输入会与预期不符合
一些例子

3.宏定义与不变量

3.1 宏(文本批量替换功能)

  • 在编程领域,"宏"就是指通过预设,对指定的文本内容进行替换
  • c语言中,通过#define预编译指令定义一个宏
  • 如下宏定义代码,在预编译阶段(即编译前),把源码中所有的PI改成对应的数字
c 复制代码
#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 函数定义------做饼

明确函数 :

  1. 接收的参数
  2. 返回值类型
  3. 具体操作

函数的定义主要包括:指明返回值、明确参数列表、编写函数逻辑三部分

5.2 函数声明------画饼

明确函数:

  1. 接收的参数
  2. 返回值的类型

函数定义是函数的实际执行部分(工人师傅),函数声明就是明确接收什么返回什么(名片),不涉及细节

5.3 函数调用与传参

调用时找到声明即可。

  • 如果调用时看不到声明,则报编译错误
  • 如果找到声明找不到定义,则报链接错误

5.4 函数返回值

  • 无论再函数内部何处,return语句的执行将立即结束函数并提交返回值
  • 函数结束后,再调用位置原地替换为返回值
  • 无需提供返回值用void

5.5 注意点

  • 函数声明可以放在另一个函数的定义中,但函数定义不可以,不同函数的定义需要隔离开来
  • 一个函数只能返回一个值

5.6 实参与形参

c 复制代码
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是闰年。
c 复制代码
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 条件表达式

  • 比较运算符:==、>、<、>=、<=、!=
  • 条件表达式:通过比较运算符对左右操作数进行运算
  • 逻辑运算符:&&、||、!

使用逻辑表达式表达复杂的组合判断逻辑,仍然是闰年问题:

c 复制代码
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适用于迭代:能够不重不漏地访问范围内所有元素
c 复制代码
    for (int i = 0; i < 1; ++i) {
        printf("%d",a++);
    }
  • for执行的顺序:

      1. int i =0;
      1. i<1;
      1. printf("%d",a++);
      1. ++i
      1. 回到2
  • for括号内的两个分号不能少

7.3 循环控制

  • break:提前终止当前整个循环
  • continue:跳过本轮循环剩下的执行内容,直接开始下一次

8.数组

8.1 数组组成

  • 基础数据类型:int、float类型的数组
  • 自定义数据类型:struct、union类型的数组

8.2 数组初始化

  • 通过列表初始化:
c 复制代码
//全部初始化
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语言中的数组特指定长数组
  • 因此定义数组元素数量必须使用常量或不变量
  • 准确来说,是编译期能直接确定数值的量
  • 已经定义好长度的数组,访问超过长度范围的索引值,会报数组越界(运行时错误)
获取已定义数组的元素数量
c 复制代码
int length = sizeof(a)/sizeof(int);

8.4 数组遍历

形如下:

c 复制代码
int a[n];
for(int i=0;i<n;i++){
	a[i]=i;
}

8.5 多维数组

  • 实际上是按顺序排列的

9. 结构体

9.1 声明结构体

c 复制代码
struct item{	//1.声明结构体
	int id;
};
struct item a;	//2.定义结构体变量

a.id = 10;		//3.访问结构体变量的成员
  • 声明:让编译器知道存在这么一个东西
  • 定义:让编译器实际生成一个东西(分配空间)
声明与定义的两种形式
  • 先声明后定义
  • 声明并定义
typedef关键字
c 复制代码
typedef unsigned long long uint64;

uint64 a;//= unsigned long long a;
  • typedef不是宏定义替换
  • typedef可以给类型取别名

注意与结构体声明并定义的区别!!

9.2 使用结构体

初始化结构体内部的成员变量
c 复制代码
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

c 复制代码
    enum day {a=1,b,c,d,e,f,g};
    enum day dd = b;
    printf("%d %d",dd,e);
    //输出:2 5

参考上述代码可知,enum相当于定义了一系列变量,默认按照递增顺序给未赋值的变量赋值。

相关推荐
重生之我在20年代敲代码21 分钟前
strncpy函数的使用和模拟实现
c语言·开发语言·c++·经验分享·笔记
爱上语文23 分钟前
Springboot的三层架构
java·开发语言·spring boot·后端·spring
编程零零七2 小时前
Python数据分析工具(三):pymssql的用法
开发语言·前端·数据库·python·oracle·数据分析·pymssql
2401_858286113 小时前
52.【C语言】 字符函数和字符串函数(strcat函数)
c语言·开发语言
铁松溜达py3 小时前
编译器/工具链环境:GCC vs LLVM/Clang,MSVCRT vs UCRT
开发语言·网络
everyStudy3 小时前
JavaScript如何判断输入的是空格
开发语言·javascript·ecmascript
jiao000015 小时前
数据结构——队列
c语言·数据结构·算法
铁匠匠匠5 小时前
从零开始学数据结构系列之第六章《排序简介》
c语言·数据结构·经验分享·笔记·学习·开源·课程设计
C-SDN花园GGbond5 小时前
【探索数据结构与算法】插入排序:原理、实现与分析(图文详解)
c语言·开发语言·数据结构·排序算法
迷迭所归处6 小时前
C++ —— 关于vector
开发语言·c++·算法