该文章Github地址:https://github.com/AntonyCheng/c-notes【有条件的情况下推荐直接访问GitHub以获取最新的代码更新】
在此介绍一下作者开源的SpringBoot项目初始化模板(Github仓库地址:https://github.com/AntonyCheng/spring-boot-init-template【有条件的情况下推荐直接访问GitHub以获取最新的代码更新】& 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[])
{
//以下这两种定义意思相同,都是有符号的int
signed int num1 = 10;
int num2 = 10;//(推荐)
//下面这一种定义是无符号的int
unsigned 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 %lu printf("十进制:num = %d\n", num); //八进制输出 %o printf("八进制:num = %o\n", num); //十六进制输出 %x printf("十六进制:num = %x\n", num); } int main(int argc, char* argv[]) { test(); return 0; }
打印效果如下:
不同的进制仅仅是数据的表现形式,并不能修改数据本身;
我们还可以对上面的代码进行修改,让输出结果看起来更直观:
#include<stdio.h> int test() { int num = 100; //十进制输出 %d %u %ld %lu printf("十进制:num = %#d\n", num); //八进制输出 %o printf("八进制:num = %#o\n", num); //十六进制输出 %x printf("十六进制: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 用%lf
printf("d = %lf\n", d);
//或者
float f = 3.14f;
// float 用%f
printf("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 转换成 double printf("%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对于各种格式说明符可以接受的参数类型。