该文章Github地址:https://github.com/AntonyCheng/c-notes
在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template & CSDN文章地址:https://blog.csdn.net/AntonyCheng/article/details/136555245),该模板集成了最常见的开发组件,同时基于修改配置文件实现组件的装载,除了这些,模板中还有非常丰富的整合示例,同时单体架构也非常适合SpringBoot框架入门,如果觉得有意义或者有帮助,欢迎Star & Issues & PR!
上一章:由浅到深认识C语言(1):C语言概论
2.C语言的类型及语句
二进制基础:
- 计算机存储的是二进制,一位二进制只能存放一个0或1:
1b
- 1B(字节) == 8b(8位二进制) :0000 0000 ~ 1111 1111
- 1KB == 1024B;1MB == 1024KB;1GB == 1024MB;1TB == 1024GB;1PB == 1024TB
数据类型基础:
数据类型 | 所占内存 |
---|---|
char(字符类型) | 1B == 8b |
short(短整型) | 2B == 16b |
int(整型) | 4B == 32b |
long(长整型) | 4B == 32b |
long long(长长整形) | 8B == 64b |
float(单精度浮点型) | 4B == 32b |
double(双精度浮点型) | 8B == 64b |
案例(验证数据类型的长度):sizeof
能够测量数据类型的长度
#include<stdio.h>
int main(int argc, char *argv[])
{printf("sizeof(char)=%dB\n", sizeof(char));printf("sizeof(short)=%dB\n", sizeof(short));printf("sizeof(int)=%dB\n", sizeof(int));printf("sizeof(long)=%dB\n", sizeof(long));printf("sizeof(long long)=%dB\n", sizeof(long long));printf("sizeof(float)=%dB\n", sizeof(float));printf("sizeof(double)=%dB\n", sizeof(double));return 0;
}
有符号数和无符号数(unsigned和signed)
-
无符号数 unsigned:数据没有符号位,自身的所有二进制位都是数据位
比如:unsigned char -------- 0000 0000~1111 1111
-
有符号数 signed(默认一般省略):二进制最高位为符号位,其他位为数据位
比如:signed char -------- xxxx xxxx (x为0或1)
负数:1xxx xxxx
正数:0xxx xxxx
所以表示范围是:1111 1111 ~ 1000 0000 ~ 0000 0000 ~ 0111 1111
案例(进制的转换):
#include<stdio.h>
#define MAX 32
int main(int argc, char *argv[])
{int i = 0, n, a[MAX];printf("请输入一个十进制数:");scanf_s("%d", &n);while (n > 0) {a[i] = n % 2;i = i + 1;n = n / 2;}printf("十进制整数转化为二进制数是:");for (; i > 0; i--)printf("%d", a[i - 1]);printf("\n");return 0;
}
案例(有无符号的数据展示):
#include<stdio.h>
int main(int argc, char* argv[])
{//以下这两种定义意思相同,都是有符号的intsigned int num1 = 10;int num2 = 10;//(推荐)//下面这一种定义是无符号的intunsigned int num3 = 10;return 0;
}
2.1.C语言关键字(32个)
数据类型关键字(12个):(数据类型存在的意义是合理分配程序的内存)
{ c h a r − 字 符 类 型 s h o r t − 短 整 型 i n t − 整 型 l o n g − 长 整 型 f l o a t − 单 精 度 浮 点 型 d o u b l e − 双 精 度 浮 点 型 u n s i g n e d − 无 符 号 数 s i g n e d − 有 符 号 数 s t r u c t − 结 构 体 u n i o n − 共 用 体 e n u m − 枚 举 v o i d − 无 类 型 \begin{cases} char-\bf{字符类型}\\short-\bf{短整型}\\int-\bf{整型}\\long-\bf{长整型}\\float-\bf{单精度浮点型}\\double-\bf{双精度浮点型}\\unsigned-\bf{无符号数}\\signed-\bf{有符号数}\\struct-\bf{结构体}\\union-\bf{共用体}\\enum-\bf{枚举}\\void-\bf{无类型}\\ \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧char−字符类型short−短整型int−整型long−长整型float−单精度浮点型double−双精度浮点型unsigned−无符号数signed−有符号数struct−结构体union−共用体enum−枚举void−无类型
补充:
struct
结构体:结构体中的成员拥有独立的空间:
struct data
{char a;short b;int c;
}
union
共用体:共用体中的成员共用一个空间:
union data
{char a;short b;int c;
}
-
enum
枚举和void
无类型-
enum
枚举:将变量要赋值的值一一列举出来:C语言中没有
bool
,所以我们可以用enum
语句将其表达出来enum BOOL{false,true}; enum BOOL bool = false;
-
void
无类型:记住不能用它定义变量,原因如下:{ i n t n u m ; 当 编 译 器 编 译 到 这 句 话 时 , 能 确 定 n u m 占 4 B ; v o i d n u m ; 当 编 译 器 编 译 到 这 句 话 时 , 不 能 确 定 n u m 占 据 多 少 空 间 ; \begin{cases}int\ num;\bf{当编译器编译到这句话时,能确定num占4B;}\\void\ num;\bf{当编译器编译到这句话时,不能确定num占据多少空间;}\end{cases} {int num;当编译器编译到这句话时,能确定num占4B;void num;当编译器编译到这句话时,不能确定num占据多少空间;
-
控制语句关键字(12个):
{ i f − 条 件 语 句 e l s e − 条 件 语 句 否 定 分 支 ( 与 i f 连 用 ) s w i t c h − 用 于 开 关 语 句 c a s e − 开 关 语 句 分 支 d e f a u l t − 开 关 语 句 中 的 其 他 分 支 f o r − 一 种 循 环 语 句 d o − 循 环 语 句 的 循 环 体 w h i l e − 循 环 语 句 的 循 环 条 件 b r e a k − 跳 出 当 前 循 环 c o n t i n u e − 结 束 当 前 循 环 , 开 始 下 一 轮 循 环 g o t o − 无 条 件 跳 转 语 句 r e t u r n − 子 程 序 返 回 语 句 ( 可 以 带 参 数 , 也 可 不 带 参 数 ) 循 环 条 件 \begin{cases} if-\bf{条件语句}\\else-\bf{条件语句否定分支(与 if 连用)}\\switch-\bf{用于开关语句}\\case-\bf{开关语句分支}\\default-\bf{开关语句中的其他分支}\\for-\bf{一种循环语句}\\do-\bf{循环语句的循环体}\\while-\bf{循环语句的循环条件}\\break-\bf{跳出当前循环}\\continue-\bf{结束当前循环,开始下一轮循环}\\goto-\bf{无条件跳转语句}\\return-\bf{子程序返回语句(可以带参数,也可不带参数)循环条件}\\ \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧if−条件语句else−条件语句否定分支(与if连用)switch−用于开关语句case−开关语句分支default−开关语句中的其他分支for−一种循环语句do−循环语句的循环体while−循环语句的循环条件break−跳出当前循环continue−结束当前循环,开始下一轮循环goto−无条件跳转语句return−子程序返回语句(可以带参数,也可不带参数)循环条件
存储类关键字(5个):
{ a u t o − 声 明 自 动 变 量 e x t e r n − 声 明 变 量 是 在 其 他 文 件 中 声 明 r e g i s t e r − 声 明 寄 存 器 变 量 s t a t i c − 声 明 静 态 变 量 c o n s t − 声 明 只 读 变 量 \begin{cases} auto-\bf{声明自动变量}\\extern-\bf{声明变量是在其他文件中声明}\\register-\bf{声明寄存器变量}\\static-\bf{声明静态变量}\\const-\bf{声明只读变量} \end{cases} ⎩⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎧auto−声明自动变量extern−声明变量是在其他文件中声明register−声明寄存器变量static−声明静态变量const−声明只读变量
register
(寄存器变量):
-
如果没显示标明 register ,就类似int num,如果num被高频繁使用系统也会放入寄存器中;
-
register int num;//显示的将num放入寄存器中;
-
寄存器的变量不能取地址 &num;
取地址用“%p”,示例如下:
#include<stdio.h> int main(int argc, char *argv[]) {int a = 16;printf("%p\n", a); }
打印效果如下:
其他关键字(3个):
{ s i z e o f − 计 算 数 据 类 型 长 度 t y p e d e f − 用 以 给 数 据 类 型 取 别 名 v o l a t i l e − 防 止 编 译 器 优 化 , 强 制 访 问 内 存 操 作 \begin{cases} sizeof-\bf{计算数据类型长度}\\typedef-\bf{用以给数据类型取别名}\\volatile-\bf{防止编译器优化,强制访问内存操作} \end{cases} ⎩⎪⎨⎪⎧sizeof−计算数据类型长度typedef−用以给数据类型取别名volatile−防止编译器优化,强制访问内存操作
typedef
示例如下(我们将int
转换成int64
):
#include<stdio.h>
typedef int int64;
int64 main(int64 argc, char *argv[])
{int64 a = 16;printf("%p\n", a);
}
此时依然能够正常打印:
volatile
示例如下:
#include<stdio.h>
int main(int argc, char *argv[])
{int num = 10;volatile int value;printf("%d",num);return 0;
}
此时 int num = 10;
就一直保存在了内存里,即使高强度反复调用也不会存入寄存器。
2.2.数据类型
数 据 类 型 { 基 本 类 型 { 整 形 : i n t 、 s h o r t 、 l o n g 字 符 型 : c h a r 实 型 ( 浮 点 型 ) { 单 精 度 实 型 f l o a t 双 精 度 实 型 d o u b l e 构 造 类 型 { 数 组 类 型 结 构 类 型 : s t r u c t 联 合 类 型 : u n i o n 枚 举 类 型 : e n u m 指 针 类 型 ( c h a r ∗ 、 i n t ∗ 、 i n t ∗ ∗ 等 ) 数据类型\begin{cases} 基本类型\begin{cases} 整形:int、short、long\\ 字符型:char\\ 实型(浮点型)\begin{cases} 单精度实型float\\双精度实型double \end{cases} \end{cases}\\ 构造类型\begin{cases} 数组类型\\结构类型:struct\\联合类型:union\\枚举类型:enum \end{cases}\\ 指针类型(char*、int*、int**等) \end{cases} 数据类型⎩⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎨⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎪⎧基本类型⎩⎪⎪⎪⎨⎪⎪⎪⎧整形:int、short、long字符型:char实型(浮点型){单精度实型float双精度实型double构造类型⎩⎪⎪⎪⎨⎪⎪⎪⎧数组类型结构类型:struct联合类型:union枚举类型:enum指针类型(char∗、int∗、int∗∗等)
常量与变量
常量:在程序运行中,其值不能被改变的量;
- 整型:
100
,125
,-100
,0
- 实型:
3.14
,0.125
,-3.232
- 字符:
'a'
,'b'
,'2'
- 字符串:
"a"
,"ab"
,"123"
示例如下:
int a = 100
char c = 'abc'
//因为一般出现在表达式右边,所以也称“右值”
变量:系统根据变量的类型开辟对应的空间,其值可以被修改;
示例如下:
int num = 10;
/*
注意:变量名num代表的是空间的内容
变量命名规则:由数字,字母和下划线组成但不能以数字开头;
*/
特点:变量在编译时为其分配相应的内存地址,可以通过名字和地址访问相应空间;
整型数据
-
整型常量:
十进制;
八进制;
十六进制;
以上三种都是整型的输出形式;
#include<stdio.h> int test() {int num = 100;//十进制输出 %d %u %ld %luprintf("十进制:num = %d\n", num);//八进制输出 %oprintf("八进制:num = %o\n", num);//十六进制输出 %xprintf("十六进制:num = %x\n", num); } int main(int argc, char* argv[]) {test();return 0; }
打印效果如下:
不同的进制仅仅是数据的表现形式,并不能修改数据本身;
我们还可以对上面的代码进行修改,让输出结果看起来更直观:
#include<stdio.h> int test() {int num = 100;//十进制输出 %d %u %ld %luprintf("十进制:num = %#d\n", num);//八进制输出 %oprintf("八进制:num = %#o\n", num);//十六进制输出 %xprintf("十六进制:num = %#x\n", num); } int main(int argc, char* argv[]) {test();return 0; }
打印效果如下:
-
整型变量:
整型变量操作:读、写
#include<stdio.h> int test() {//局部变量不初始化,内容随机//int num;int num = 0;printf("num = %d\n", num);//读操作,取值num = 100;//写操作,赋值printf("num = %d\n", num);//读操作,取值 } int main(int argc, char* argv[]) {test();return 0; }
打印效果如下:
如果我们想从键盘获取用户输入的话,我们可以在以上代码做改进:
#include<stdio.h> int test() {//局部变量不初始化,内容随机//int num;int data = 0;int num = 0;printf("num = %d\n", num);//读操作,取值num = 100;//写操作,赋值printf("num = %d\n", num);//读操作,取值data = num;//num是读,data是写printf("data = %d\n", data);//获取键盘输入printf("请输入一个整型数据:");scanf_s("%d", &data);//&data 代表data对应空间的起始地址printf("data = %d", data); } int main(int argc, char* argv[]) {test();return 0; }
打印效果如下:
有/无符号短整型
(un/signed) short(int)
----2个字节有/无符号基本整型
(un/signed) int
-------------4个字节有/无符号长整型
(un/signed) long(int)
-------4个字节示例如下:
#include<stdio.h> int test() {int num1 = 0;// %d 有符号整型输出printf("num1 = %d\n", num1);unsigned int num2 = 0;// %u 无符号整型输出printf("num2 = %u\n", num2);long num3 = 0;// %ld 有符号长整型输出printf("num3 = %ld\n", num3);unsigned long num4 = 0;// %lu 无符号长整型输出printf("num4 = %lu\n", num4);short num5 = 0;// %hd 有符号短整型输出printf("num5 = %hd\n", num5);unsigned short num6 = 0;// %hu 无符号短整型输出printf("num6 = %hu\n", num6); } int main(int argc, char* argv[]) {test();return 0; }
打印效果如下:
实型数据(浮点型)
实型常量:
实型常量也称为实数或者浮点数;
十进制形式:由数字和小数点组成:0.0
、0.12
、5.0
;
指数形式:123e3
表示 123 × 1 0 3 123×10^3 123×103;
不以 f
结尾的常量是 double
类型;
以 f
结尾的常量是 float
类型;
说明实例:
int fun() {//不以 f 结尾的实型常量为 double 类型printf("sizeof(3.14) = %d\n", sizeof(3.14));//以 f 结尾的实型常量为 float 类型printf("sizeof(3.14f) = %d\n", sizeof(3.14f));
}
int main(int argc, char* argv[]) {fun();return 0;
}
打印效果如下:
所以下面这个例子就有错误:
int fun() {//所以下面这个例子就有错误//float f = 3.14; //会报错double d = 3.14;// double 用%lfprintf("d = %lf\n", d);//或者float f = 3.14f;// float 用%fprintf("f = %f\n", f);
}
int main(int argc, char* argv[]) {fun();return 0;
}
我们将它们输出出来:
实型变量:
单精度(float)
和双精度(double)
;
float型:占4B,7位有效数字,指数-37到38;
double型:占8B,16位有效数字,指数-307到308;
字符----''
作用
-
字符常量:
直接常量:用单引号括起来----
'a'
'b'
等;示例如下:
int fuc() {// %c 输出的是字符printf("%c\n", 'a');//ch储存的是'a'的ASCII值,单引号表示的取字符的ASCII值char ch = 'a';printf("ch = %c\n", ch);// %d 输出的是ASCII值printf("ch = %d\n", ch); } int main(int argc, char* argv[]) {fuc();return 0; }
打印效果如下:
'a'
单引号表示取 a 的ASCII
值,字符在计算机及存储的是ASCII
;如果我们要获取一个字符:
int fuc() {char ch;printf("请输入一个字符:");//scanf里的 %c 只能提取一个字符//scanf_s("%c", &ch);//等同于:ch = getchar();printf("ch = %c\n", ch);printf("ch = %d\n", ch); } int main(int argc, char* argv[]) {fuc();return 0; }
打印效果如下:
案例:输入字符
'abc'
,只取其中的'a'
和'b'
int fuc() {char ch1, ch2;printf("请输入'abc':");ch1 = getchar();ch2 = getchar();printf("ch1 = %c\n", ch1);printf("ch2 = %c\n", ch2); } int main(int argc, char *argv[]) {fuc();return 0; }
打印效果如下:
案例改编:输入字符
'abc'
,只取其中的'a'
和'c'
int fuc() {char ch1, ch2;printf("请输入'abc':");ch1 = getchar();getchar();ch2 = getchar();printf("ch1 = %c\n", ch1);printf("ch2 = %c\n", ch2); } int main(int argc, char *argv[]) {fuc();return 0; }
打印效果如下:
案例原理:
getchar()
函数能够依次在键入字符串中拿取字符,若没有被赋值项,则该字符被丢弃; -
转义字符:
-
\n
换行字符; -
\t
跳格,等于tab
; -
\\
反斜杠; -
%%
百分号; -
\0
将字符转义成数字示例如下:
int fuc() {printf("%d\n",0);printf("%d\n", '\0'); } int main(int argc, char *argv[]) {fuc();return 0; }
打印效果如下:
-
字符串----""
作用
案例:程序员的第一条代码;
int func() {// %s 就是输出字符串// %s 从字符串的首元素,逐个字符输出,遇到'\0',结束printf("%s", "hello world\n");// 所以系统会在字符串末尾自动添加一个结束字符 '\0'printf("\"hello world\"的内存大小是 %d\n", sizeof("hello world"));
}
int main(int argc, char *argv[]) {func();return 0;
}
打印效果如下:
问:为什么 hello world
占12个比特大小而不是11个比特大小呢?
答:因为字符串默认以 \0
结尾;
注意:""
双引号取的是字符串的首元素地址,而''
单引号取的是字符串的首元素ASCII值;%s
从字符串的首元素,逐个字符输出,遇到'\0'
,结束;
示例如下:
int func() {//双引号取的是字符串的首元素地址printf("%d\n", "hello world");//%s 从字符串的首元素,逐个字符输出,遇到'\0',结束printf("%s\n", "hello wo\0rld");
}
int main(int argc, char *argv[]) {func();return 0;
}
打印效果如下:
总结一下
格式化输出 | 意义 |
---|---|
%d | 十进制有符号整数 |
%u | 十进制无符号整数 |
%ld | 十进制有符号长整型 |
%lu | 十进制无符号长整型 |
%hd | 十进制有符号短整型 |
%hu | 十进制无符号短整型 |
%o | 八进制有符号整数 |
%x | 十六进制有符号整数 |
%f | 浮点数 |
%e | 指数形式的浮点数 |
%lf | double型浮点数 |
%c | 单个字符 |
%s | 字符串 |
%p | 指针的值 |
特殊应用示例如下:
int func() {printf("###################\n");// %5d 表示占5个终端位宽 右对齐printf("##%5d##\n", 123);// %-5d 表示占5个终端位宽 左对齐printf("##%-5d##\n",123);// %05d 表示占5个终端位宽 右对齐 空位用0补齐printf("##%05d##\n", 123);//千万不能写 %-05d//printf("##%-05d##",123);没办法输出出来// %5.2f 5表示总位宽为5,2表示小数点保留俩位printf("##%5.2f##\n", 3.1);
}
int main(int argc, char *argv[]) {func();return 0;
}
打印效果如下:
格式化 | 意义 |
---|---|
%5d | 表示占5个终端位宽 右对齐 |
%05d | 表示占5个终端位宽 右对齐 空位用0补齐 |
%-5d | 表示占5个终端位宽 左对齐 |
%5.2f | 5表示总位宽为5,2表示小数点保留俩位 |
typedef
类型重定义:为已有的类型重新取个别名步骤
- 用已有的类型定义一个变量;
- 用别名替换变量名;
- 在整个表达式的前方加上
typedef
;
案例一:给 int
取别名 INT32
typedef int INT32;
案例二:给一个数组 arr[5]
取一个别名
-
step1:
int arr[5];
-
step2:
int ARR[5];
-
step3:
typedef int ARR[5];
-
最终如下:
typedef int ARR[5]; ARR arr;//arr就是一个拥有5个int元素的数组
数据的混合运算
数据有不同的类型,不同类型数据之间进行混合运算时必然涉及到类型的转换问题;
转换的方法有两种: { 自 动 转 换 : 遵 循 一 定 的 规 则 , 由 编 译 系 统 自 动 完 成 ; 强 制 类 型 转 换 : 把 表 达 式 的 运 算 结 果 强 制 转 换 成 所 需 的 数 据 类 型 ; \begin{cases}\bf{自动转换:遵循一定的规则,由编译系统自动完成;}\\\bf{强制类型转换:把表达式的运算结果强制转换成所需的数据类型;}\end{cases} {自动转换:遵循一定的规则,由编译系统自动完成;强制类型转换:把表达式的运算结果强制转换成所需的数据类型;
-
自动类型转换(图示):
案例:有符号和无符号的转换;
int way() {int data1 = -20;unsigned int data2 = 10;//有符号和无符号计算时,先将有符号转换成无符号//则这里会将 -20 转换成无符号(-20的补码,很大的数)if (data1 + data2 > 0) {printf(">0\n");}else if(data1 + data2 < 0) {printf("<0\n");} } int main(int argc, char *argv[]) {way();return 0; }
打印效果如下:
案例:
int
和double
的转换;int way() {int data = 10;// 3.14 是 double 类型,data 是 int 类型//应该先把 int 转换成 doubleprintf("%d\n", sizeof(data + 3.14)); } int main(int argc, char *argv[]) {way();return 0; }
打印效果如下:
案例:
short
和char
的类型转换;int way() {char ch = 'a';short data = 20;//由于 char short 自身字节数过小,很容易溢出//所以只要它们参加运算,都会被系统转换为 int 类型printf("%d\n", sizeof(ch + ch));printf("%d\n", sizeof(ch + data));printf("%d\n", sizeof(data + data)); } int main(int argc, char *argv[]) {way();return 0; }
打印效果如下:
案例:
double
和float
的类型转换;int way() {// 3.14 是 double 类型// 3.14f 是 float 类型printf("%d\n", sizeof(3.14 + 3.14f)); } int main(int argc, char *argv[]) {way();return 0; }
打印效果如下:
-
强制转换:通过类型转换运算来实现;
公式如下:
( 类 型 说 明 符 ) 表 达 式 (类型说明符) 表达式 (类型说明符)表达式
注意:类型说明符要用小括号给括起来;
功能:
把表达式的运算结果强制转换成类型说明符所表示的类型;
例如下:
int way() {float x = 3.14f;int j;//强制类型转换只是在当前语句起作用,并没有改变 x 是 float 的实时;j = (int)x; printf("%d\n", j); } int main(int argc, char *argv[]) {way();return 0; }
打印效果如下:
-
无论是强制类型转换还是自动转换,都只是为了本次运算的需要,而对变量的数据长度进行的临时性转换,而不改变数据定义的类型;
-
为什么printf()用%f输出double型,而scanf却用%lf呢?
答:printf的%f说明符的确既可以输出float型又可以输出double型。 根据"默认参数提升"规则(在printf这样的函数的可变参数列表中 ,不论作用域内有没有原型,都适用这一规则)float型会被提升为double型。因此printf()只会看到双精度数。参见问题15.2。
对于scanf,情况就完全不同了,它接受指针,这里没有类似的类型提升。(通过指针)向float存储和向double存储大不一样,因此,scanf区别%f和%lf。
下表列出了printf和scanf对于各种格式说明符可以接受的参数类型。