快速上手C语言【上】(非常详细!!!)

目录

[1. 基本数据类型](#1. 基本数据类型)

[2. 变量](#2. 变量)

[2.1 定义格式 和 命名规范](#2.1 定义格式 和 命名规范)

[2.2 格式化输入和输出(scanf 和 printf)](#2.2 格式化输入和输出(scanf 和 printf))

​编辑

[2.3 作用域和生命周期](#2.3 作用域和生命周期)

[3. 常量](#3. 常量)

[4. 字符串+转义字符+注释](#4. 字符串+转义字符+注释)

[5. 操作符](#5. 操作符)

[5.1 双目操作符](#5.1 双目操作符)

[5.1.1 算数操作符](#5.1.1 算数操作符)

[5.1.2 移位操作符](#5.1.2 移位操作符)

[5.1.3 位操作符](#5.1.3 位操作符)

[5.1.4 赋值操作符](#5.1.4 赋值操作符)

[5.1.5 关系操作符](#5.1.5 关系操作符)

[5.1.6 逻辑操作符](#5.1.6 逻辑操作符)

[5.1.7 下标引用、函数调用和结构成员](#5.1.7 下标引用、函数调用和结构成员)

[5.2 单目操作符](#5.2 单目操作符)

[5.2.1 不改变原操作数](#5.2.1 不改变原操作数)

[5.2.2 改变原操作数](#5.2.2 改变原操作数)

[5.3 三目运算符](#5.3 三目运算符)

[5.4 逗号表达式](#5.4 逗号表达式)

[5.5 操作符的属性](#5.5 操作符的属性)

[6. 整形提升和类型转换](#6. 整形提升和类型转换)

[7. 分支语句和循环语句](#7. 分支语句和循环语句)

[7.1 分支语句(选择结构)](#7.1 分支语句(选择结构))

[7.1.1 if语句](#7.1.1 if语句)

[7.1.2 switch语句](#7.1.2 switch语句)

[7.2 循环语句](#7.2 循环语句)

[7.2.1 while](#7.2.1 while)

[7.2.2 for](#7.2.2 for)

[7.2.3 do...while](#7.2.3 do...while)

[7.2.4. goto语句](#7.2.4. goto语句)

[7.3. 一段有趣的代码](#7.3. 一段有趣的代码)


1. 基本数据类型

也叫 内置类型 ,即编程语言自带的基本数据类型,无需额外定义或导入,直接就能定义变量,如下:

1:char //字符数据类型

典型的比如ASCII 字符表

第一部分:(看看就行)

第二部分:常用的就三小段:字符0 ~ 9,A ~ Z,a ~ z;只需记住0,A,a对应的十进制,往后递增加1,大小写间的十进制差是32。并且 char 类型(字符类型)的数据在参与表达式运算时用其对应的十进制数,所以也把它被归为 整形 一类,即数学中的整数。

接着是:每个 char 类型数据的大小为1个字节(byte)。

**字节是计算机划分数据存储的基本单位,1个字节又划分成8个比特位(bit),即8个0或1组成的串,**比如,ASCII表用二进制表示就是:00000000 ~ 01111111;那么8个比特位能表示的数据范围就是 00000000~11111111,即0~255,所以是不是说 char 可表示的十进制数据范围就是0~255?

其实不是的。

char 可定义变量的有效数据范围是:-128 ~ 127;0~255指 unsigned(无符号的) char 的范围,这是一种 无符号型的数据,char是有符号的。现在你只需要知道的是:不同类型可定义变量的有效数据范围是不同的,范围外的就叫 "溢出"。

同属整形家族还有:

2. short //短整型,2byte == 16bit,范围:-2^15 ~ 2^15 - 1,即-32768 ~ 32767

unsigned short 范围 0 ~ 2^16,即0~65535

3. int //整形,定义整数常用,4byte == 32bit,范围:-2^31 ~ 2^31 -1,即:正负21亿多

unsigned int 范围 0~2^32,即42亿多;但是经常使用的是:size_t(无符号整形),注意 其在32(x86)位系统下,4个字节;在64(x64)位系统下,8个字节。

  1. long //长整形,在 32(x86)位系统上,通常是 4 byte;在 64(x64)位系统上, 通常是 8 byte

  2. long long //更长的整形,8byte

然后是浮点数,和整形的规则不同,现在大家把它们当 普通小数类型 来用就行

6. float //单精度浮点数,4byte

7. double //双精度浮点数,8byte

8. 指针类型(后面讲)

2. 变量

2.1 定义格式 和 命名规范

**定义格式:**数据类型 变量名;

形象的解释一下就是:拿着图纸造房子 ------> 不同的设计图纸对应不同的数据类型,造出来的房子是一个个实体呀,属于开了物理空间的 ,其大小就对应着设计图纸的参数,也就是数据类型的大小,比如int有4个字节,char只有一个字节; 至于这个房子里放什么,可能随着时间一直在变化,所以称 这个房子 就是个变量 ;但不管你放什么,都不能超过其大小,比如这个房子的大小就200平,你偏要往里修个1000平的游泳池,放不下呀,也就是我们前面说的 "溢出" 了;还有,这个房子 最后叫什么名,也不是设计图纸要管的事,属于开发商的自定义,即 变量名

【***.c文件叫源文件,就是写代码的地方;一切的字母和符号都要用 英文输入法;" ; " 是一条语句结束的标志,不可遗漏】

比如,在test.c源文件中:

cpp 复制代码
int main()//程序执行入口,只能有一个,一定要写!!!
{
	char a, b = 'b', c, d;//定义并局部初始化;定义多个变量用逗号隔开;单个字符的使用要用单引号' '
	int age = b-18;//定义并赋值(=)初始化,这是好的编程习惯;编译器默认向上查找变量b,就是说:一定要:先定义,再使用!!!
	float weight = 53.5f;//小数默认为double类型,定义float类型可带后缀f

	return 0;//返回执行结果
}

命名规范有以下要求:

1.只能由字母(包括大写和小写)、数字和下划线( _ )组成。

2.不能以数字开头。

3.长度不能超过63个字符。

4.变量名中区分大小写的。

5.变量名不能使用关键字(语言本身预先设定好的),比如:

2.2 格式化输入和输出(scanf 和 printf)

包含头文件:#include<stdio.h>

(关于**<头文件(***.h)>**,现在你只要知道是:C语言本身已经写好的东西(比如下面的scanf和printf)都是被打包好的,你安装C编译环境时,它们就被下载到你的本地PC上;你要用的时候,就要告诉编译器去哪里找,方式就是:#include<****.h>)

输入:scanf

(如果你用Visual Studio,加上:#define _CRT_SECURE_NO_WARNINGS 1 否则错误信息会建议你使用 scanf_s,但这个是微软自己搞的,不属于官方原生)

从stdin(标准输入流,就是你的键盘输入,其实被放到一个类似文件的管理系统中,我们在屏幕上看到的输入就是从这个系统里读取出来,再呈现给我们)读取数据,并根据参数格式将其存储到附加参数(指针变量)所指向的位置,也就是我们定义好的变量

比如:

关于这个字符序列(C字符串):

**空白字符:**该函数将读取并忽略下一个非空白字符之前遇到的任何空白字符(空白字符包括空格、换行符\n和制表符\t。

**非空白字符,除了格式说明符(%):**都会导致scanf函数从流中(键盘)读取下一个字符,将其与该非空白字符进行比较,如果匹配,则丢弃该字符,继续读下一个字符。如果字符不匹配,则函数失败,返回并保留流的后续字符未读。

格式说明符: 由初始百分比符号(%)组成的序列表示格式说明符,用于指定要从流中检索的数据的类型和格式,并将其存储到附加参数所指向的位置。

常用的有:%c ------ 字符类型

%d ------ 有符号十进制整数

%u ------ 无符号十进制整数

%f ------ 十进制单精度浮点数

%lf ------ 十进制双精度浮点数

%s ------ C字符串

%x ------ 无符号十六进制整数(0~9,a~f,A~F),一般以0x或0X开头(0是数字零)

%o ------ 无符号八进制整数(0~7)

至于什么意思,看下面的例子:

也就是说,我们的输入根本就不是我们以为的整型,浮点型,而都是文本字符,程序是根据 格式说明符 来处理读到的字符,修改变量内容的。

scanf 格式说明符的完整原型其实是下面这个:

%[*][width][length]说明符

其中:* 表示一个"忽略"操作,即从stdin中读取字符,但是不对变量内容进行修改

width:宽度,指定当前读取操作中要读取的最大字符数( 加上终止符\0**),遇到空格字符提前结束读取**(可选)

length:指的是数据类型的长度修饰符,用于指定数据类型的大小,以便于正确地读取或打印数据;不同的 length 修饰符影响与类型相关的转换。其通常和说明符搭配使用:

(黄色行表示C99引入的说明符和子说明符)

现在给大家举个测试例子:

大家平常的使用场景,一般用不到**[*][width][length]**,直接 "%+常用说明符" 就行,但这些丰富的用法总得见一见,学一学,用一用。

接着是格式化输出:printf

和scanf不同,其作用是:把C字符串写到stdout(标准输出流),即 屏幕上

关于这个C字符串,有两种情况:

情况一:不包含格式说明符(%):

也可以输出中文字符:printf("你好,我的名字是***")

情况二:包含格式说明符(%),**则其后面的附加参数(变量的值)将被格式化并插入到结果字符串中,以替换它们各自的说明符。**常用格式说明符和scanf的一样,这里再加几个:

%p ------ 指针(地址)

%e(小写)和 %E(大写)------ 科学计数法,通常对浮点数使用。

比如:12345.6789 科学计数法表示为:1.23456789 x 10^4

%e格式输出就是:1.234568e+04 【默认保留6位小数,并且四舍五入;e+04表示将小数点向右移动 4 位,就是 12345.680000,较原数产生了精度损失】

-0.0034 科学计数法表示为:-3.4 x 10^-3

%E格式输出就是:-3.400000E-03 【默认保留6位小数,并且四舍五入;E-03表示将小数点向左移动 3 位,就是 -0.003400,较原数没有精度损失】

如下示例:

cpp 复制代码
int main()//执行入口
{
	int age = 18;
	double weight = 56.5;
	const char* name = "ZhangSan";//常量字符串的定义初始化,先学着用
	printf("Name=%s;Age=%d;Weight=%lf\n", name, age, weight);

	printf("addree(age)=%p\n", &age);

	double d1 = 12345.6789;
	double d2 = -0.0034;
	printf("%e,  %E\n", d1, d2);

	return 0;
}

输出:

(至于为什么地址是一串数字编号,我们后面再看,现在你只要知道是什么就行)

printf的格式说明符遵循以下原型:

%[flags][width][.precision][length]说明符

和scanf一样,末尾的说明符字符是最重要的组成部分,其它的都是子说明符,属于可选可不选。

1:先来看[width]:(每个输出只能二选一)

number: 要打印的最小字符数。如果要打印的值比这个数字短,结果将用空格填充。即使结果更大,值也不会被截断

*: 宽度不是在格式字符串中指定的,而是作为必须格式化的参数之前的附加整数值参数指定的。

比如:

并且通过num1的输出可以发现:默认右对齐

如果要左对齐,就要添加 flags 标志。

2:[flags](可任意组合使用)

2.1 如果不写入任何符号,则在值之前插入空白,不是空格

2.2 ' - ':左对齐

2.3 '+':即使是正数,也强制在结果前加上正号(+),因为默认情况下,只有负数前面有-号

比如:输入一个整数,要求输出占20个字符,左对齐,带正负号

cpp 复制代码
int num = 0;
scanf("%d", &num);
printf("num=%-+20d\n", num);//或者printf("num=%+-*d\n", 20, num);

如果就是要求默认的右对齐,但是不够的字符要指定用零(0)而不是空格左填充,就要用符号:

2.4 '0' 字符零,修改为如下:

cpp 复制代码
printf("num=%+020d\n", num);//或者printf("num=%0+*d\n", 20, num);

(注意:左对齐时,指定填充就失效了)

2.5 '#':

2.5.1 与o、x或X说明符一起使用时,对于不同于零的值,值的前面分别加0、0x或0X

比如:

cpp 复制代码
int num4 = 100;
printf("%#o, %#x, %#X\n", num4, num4, num4);

输出:0144, 0x64, 0X64

2.5.2 如果与 g 或 G 一起使用时,即使后面没有更多数字,它也会强制写入输出包含小数点;说明符'g'和'G'的意思是:使用最短形式表示浮点数。

比如:

cpp 复制代码
double num5 = 100.00000;
printf("%lf, %g, %#g\n", num5, num5, num5);

float num6 = 100.12056;
printf("%lf, %G, %#G\n", num6, num6, num6);

输出:'g'说明符会 根据数值的大小自动选择使用 常规小数形式%f)或 科学计数法%e),以确保输出尽可能简洁。具体来说,%g 会根据浮点数的大小和精度选择最合适的格式,去除不必要的尾随零和小数点。

3. [.precision] 精度 (每个输出只能二选一)

.number

对于整数说明符(d, u, o, x, X):效果相当于[flags]的'0'指定左填充;精度为0表示无操作。

**(常用)**对于浮点数(l, lf, e, E): number就是小数点后要打印的位数(默认是6位)

对于'g'和'G'说明符:有效数字的最大数目

对于's'说明符:这是要打印的最大字符数。默认情况下,将打印所有字符,直到遇到结束的\0字符。 如果指定的周期没有明确的精度值,则假定为0

**.*:**精度不是在格式字符串中指定的,而是作为必须格式化的参数之前的附加整数值参数指定的

看下面的例子:

cpp 复制代码
int num7 = 10;
printf("%.0d,%.1d,%.*u\n", num7, num7, 10, num7);

float num8 = 67.34508;
printf("%f, %.2f; %E, %.*E\n", num8, num8, num8, 3, num8);

printf("%g, %.1g, %.*g, %.4g\n", num8, num8, 2, num8, num8);//有效数字是从左往右第一个非零数开始

const char* str = "hello world!";
printf("%s, %.s, %.3s, %.20s", str, str, str, str);

输出:

4:[length] 长度

和前面scanf的[length]用法大致一致,此处不再赘述,贴一张参考表格:

2.3 作用域和生命周期

变量分为:局部变量(变量所在的局部范围) 和 全局变量(整个项目代码)

举个例:

cpp 复制代码
int global = 10;//全局变量

int main()
{
	int local = 20;//局部变量
	printf("global=%d, local=%d\n", global, local);
	return 0;
}

总结一下就是:{ } 就叫一个局部域 ,这个域里定义的变量就叫局部变量 ,这些变量只能在这个域里使用(作用域) ,出了这个域,这些变量就会被销毁,即所占空间还给操作系统,不受你的代码程序管理了,从创建到销毁的时间段就叫做这个变量的生命周期;不在任何{}的变量,就是全局的,可被任意一个局部域使用,且其生命周期是程序结束时。

其次,一个局部域可以通过{ }被不断划分成多级局部域;并且同级域内不能 重定义重名,全局域也是如此;不同级局部域之间可以重名,因为生命周期不同。

如下示例:

最后,当局部变量和全局变量冲突时(重名),局部优先。

cpp 复制代码
int agc = 10;
int main()
{
	int agc = 20;
	printf("agc=%d\n", agc);//输出:agc=20
	return 0;
}

3. 常量

不同于变量,比如:年龄,体重,薪资等; 生活中的有些值是不变的,比如:圆周率,身份证号码,血型,......

C语言中的常量和变量的定义的形式有所差异,分为以下以下几种:

**1. 字面常量:**比如:3.14,100,'a',"hello world!",......

**2. const 修饰的常变量:**本质还是变量,但是不能直接通过变量名修改其内容,比如:

修饰局部变量:

cpp 复制代码
int main()
{
    const int a = 10;//局部变量
    a = 20;//报错
    return 0;
}

修饰全局变量: 它会默认具有内部链接属性,即 变量只在定义它的源文件中可见,其他源文件无法引用**,看对比:**

3. #define 定义的标识符常量:就一个词**"直接替换"**,比如:

cpp 复制代码
#define NUM1 10
int main()
{
	#define NUM2 20
	int a = NUM1 + NUM2;//处理成:int a = 10 + 20;
	printf("%d\n", a);//输出:20
	return 0;
}

**4. 枚举常量:整形常量,关键字(enum),**可穷举,比如:

cpp 复制代码
enum Colour//颜色
{
	Red = 2,
	Green = 4,
	Yellow = 8
};

int main()
{
	enum Option//选项
	{
		Stop,//默认为0,往下递增+1
		Pass,
		Caution
	};
	printf("%d :> %d\n%d :> %d\n%d :> %d\n", Red, Stop, Green, Pass, Yellow, Caution);
	return 0;
}

输出:

这里只是简单介绍让大家能看懂,学着用;实际的运用场景是更丰富,更灵活的,需要不断的项目代码积累量,才能体会。

4. 字符串+转义字符+注释

关于 字符串,前面已经介绍过并使用过了,这里就简单提一下:

用双引号 " " 引起来的一串连续字符,结束标志是'\0'

定义一个常量字符串:const char* str = "*********";

输出:printf("%s\n", str); 或者 printf(str);

一个常用的库函数:strlen,返回字符串的长度,即字符数,不包含'\0'

包含头文件:#include<string.h>

如下代码:

cpp 复制代码
int main()
{
    const char* str = "hello world!";
    size_t len_str = strlen(str);
    printf(str);
    printf("\nlen_str=%u\n", len_str);
    return 0;
}

输出:

下面,我们来看 转义字符:顾名思义就是转变意思

常见的有:

注:

三字符序列是 C 语言标准中为支持某些不包含全套 ASCII 字符集的键盘而引入的一组字符组合,这些组合会被编译器解释为其他符号。例如,??) 被解释为 ],而 ??( 被解释为 [。为了防止这些三字符序列在代码中意外地被编译器解释为其他符号,C 语言提供了 \? 这个转义字符。虽然三字符序列在现代编译器中已经很少使用,但 \? 作为一个转义字符仍然存在,以避免与这些序列发生冲突。

看下面的例子:

cpp 复制代码
int main()
{
	char c1 = '\'';
	printf("%c\n", c1);

	const char* str1 = "\"hello\", \"world\"\f";
	printf(str1);

	printf("\nD:\code\test.c\n");
	printf("D:\\code\\test.c\n\n");

	int i = 0;
	for (; i < 10; i++)
	{
		printf("\a");
	}

	//\b将输出光标向左移动一个字符,但不会删除字符,但是会被紧接着的输出覆盖
	printf("start test char of '\\b'!\b\n");
	printf("start test char of '\\b'!\b\b\b\n");
	printf("start test char of '\\b'!\b\b\b\b\b\b\b****\n\n");

	printf("start test char of '\\r'!\n");
	printf("start test char of '\\r'!");
	printf("\rstart test\r char of '\\r'!\n\n");//覆盖

	printf("\130\t\x30\test\n");//补空格
	return 0;
}

输出:

(注意:输出的都是单个字符)

注释:

其实在前面的示例中小编就一直在用,目的之一就是:有些代码需要标注一下是干啥的;或者代码太多,避免逻辑混乱;再或者你要把代码给别人看/用,减少他人的理解成本;...... 这绝对是个利己利他,调高效率的编程好习惯!

另一个常见用途是,有的代码需要拿来做测试,但是发行版本又不需要,那么此时可以不用删除,注释掉就行。

注释有两种风格(C/C++都能用):

C语言风格的注释: /*xxxxxx*/

缺陷:不能嵌套注释,即每个/*配对最近的*/

C++风格的注释: //xxxxxxxx

可以注释一行也可以注释多行

再简单举个例子:

cpp 复制代码
int main()
{
	/*
	int age = 10;
	const char* name = "zhangsan";
	double weight = 56.8;
	*/

	//接下来是一段测试代码,探究scanf和printf
	int a = 10;//定义变量a并初始化为10
	scanf("%2d", &a);//只读取2个字符,当整数处理
	printf("%-+10.6o\n", a);//输出:左对齐,带正负号,占10位,精度为6,以八进制输出

	//......

	return 0;
}

当然,这里只是演示,告诉大家怎么用,实际中的注释应该尽量言简意赅,重点突出!

5. 操作符

是由操作数(如变量、常量)和操作符组成的一段代码,就叫 表达式,它会被计算并返回一个值;单独的一个操作数也可以被视为一个表达式,这种表达式的计算结果就是操作数本身。

5.1 双目操作符

即,需要左右两个操作数。

5.1.1 算数操作符

+(加) -(减) *(乘) / %(取余数)

%操作符只能用于整数间,比如:5%2 = 1;其它的整数,浮点数都行。

对于 / 操作符:如果两个操作数都为整数,执行整数除法 取模(商),比如:5/2=2

只要有一个操作数是浮点数,则执行正常的浮点数相除,比如:5/2.0=2.5

5.1.2 移位操作符

<< 左移操作符

>> 右移操作符

移位操作符的操作数只能是整数,位指 "比特位"

需要预备知识:《C语言---数据的存储》点2.1和点2.2

先看:<< 左移操作符

代码验证一下:

cpp 复制代码
int main()
{
    int num1 = 10;
    int num2 = num1 << 2;
    printf("num1=%d, num2=%d\n", num1, num2);
    return 0;
}

输出:

接着看:>> 右移操作符

代码验证一下,你当前的编译器是逻辑右移,还是算数右移:

cpp 复制代码
int main()
{
    int num3 = -1;
    int num4 = num3 >> 2;
    printf("num3=%d, num4=%d\n", num3, num4);
    return 0;
}

小编当前演示示例是算数右移:

警告: 对于移位运算符,不要移动负数位,这个是标准未定义的。

5.1.3 位操作符

这里的位,同样是指 "比特位"。两个操作数也必须是整数。

&:按位与,有0就是0

| :按位或,有1就是1

^:按位异或,相同为0,相异为1

需要预备知识:《C语言---数据的存储》点2.1和点2.2****

如下示例:

代码验证一下:

cpp 复制代码
int main()
{
    int num5 = -1;
    int num6 = 2;
    int num7 = num5 & num6;
    int num8 = num5 | num6;
    int num9 = num5 ^ num6;
    printf("num5=%d, num6=%d, num7=%d, num8=%d, num9=%d", num5, num6, num7, num8, num9);
    return 0;
}

输出:

运用示例:实现两个整数的交换

cpp 复制代码
int main()
{
    int a = 0;
    int b = 0;
    scanf("a=%d, b=%d", &a, &b);
    //思路一:创建中间临时变量tmp
    int tmp = a;
    a = b;
    b = tmp;

    //要求:不能创建其它变量
    //思路二:运用加减法
    a = a + b;
    b = a - b;//b=a+b-b=a
    a = a - b;//a=a+b-a=b

    //(重点!!!)思路三:利用 ^
    a = a ^ b;
    b = a ^ b;//b=a^b^b = a ^ 0=a;特性1:两个相同值^为0,并与顺序无关;特性2:任何值与0异或,都不变
    a = a ^ b;//a=a^b^a=b^0=b

    printf("a=%d, b=%d\n", a, b);
    return 0;
}

5.1.4 赋值操作符

=

前面就一直在用,覆盖变量原先的值

cpp 复制代码
 double f1 = 34.5;
 f1 = 70.0;

 int a = 1;
 int b = 2;
 int c = 3;
 a = c = b * a + 4;//连续赋值,从右往左

 //但是同样的语义,可以写成:(更加清晰爽朗而且易于调试,推荐)
 c = b * a + 4;
 a = c;

复合赋值符:

+=:a += b 就是 a = a + b;

同理还有: -=,*=,/=,%=,>>=,<<=,&=,!=,^=

5.1.5 关系操作符

> 大于 >=大于或等于

<小于 <=小于或等于

!=不等于 ==等于

关系比较的结果:0为假,非零为真

如下代码:

cpp 复制代码
int main()
{
    int a = 10;
    int b = 20;
    printf("%d, %d, %d, %d, %d, %d\n", a > b, a >=b, a < b, a<=b, a != b, a == b);
    return 0;
}

输出:

5.1.6 逻辑操作符

&& 逻辑与:左右两个表达式同时为真(非0)才返回真,否则为假(0)

|| 逻辑或 :左右两个表达式只要一个为真,就返回真

(从左往右)

如下代码:

cpp 复制代码
int main()
{
    int c = 20;
    int d = 30;
    int e = c < d && c == 20;
    int f = c >= d && d == 30;
    int g = e || f;
    int h = f || d <= c;
    printf("%d, %d, %d, %d\n", e, f, g, h);
    return 0;
}

输出:

5.1.7 下标引用、函数调用和结构成员

分别在数组,函数,结构体部分讲解。

5.2 单目操作符

即,只需要一个操作数。

5.2.1 不改变原操作数

- 负值:将正值变负,负值变正

**+ 正值:不能把负值变正,**表示该数值为正,为了代码的可读性或语义明确性,但较少使用

! 逻辑反操作:真变假,假变真

~ 对一个数的二进制按位取反:0变1,1变0

sizeof 操作数的类型长度(以字节为单位)

(类型) 强制类型转换

&(取地址)和 *(解引用)在 指针 部分细讲。

如下代码:

cpp 复制代码
int main()
{
    int a = 10;
    int _a = -a;

    int b = -20;
    int _b = +b;

    int c = _a > b;
    int c1 = !c;
    int c2 = !c1;

    int d = 1;//二进制:0~1
    int _d = ~d;//二进制:1~0,输出十进制为:-2
    printf("%d, %d; %d, %d; %d, %d, %d; %d, %d\n", a, _a, b, _b, c, c1, c2, d, _d);

    //常用:sizeof,注意我的书写形式
    int e = sizeof(char);
    double f = 3.14;
    int g = sizeof(f);
    printf("%d, %d\n", e, g);
    printf("%d, %d, %d\n", sizeof(int), sizeof(a), sizeof a);

    //强制类型转换(相近类型: 整形,浮点型,指针)
    int num1 = 10;
    double num2= (double)(num1 / 4);
    double num3 = (double)num1 / 4;
    printf("%lf, %lf\n", num2, num3);
    return 0;
}

输出:

5.2.2 改变原操作数

前置/后置: ++ , -- ;每次+/- 1

直接来看例子:

cpp 复制代码
int main()
{
    int a = 10;
    int b = ++a;//前置++:a先加1:a = a +1==11;然后再把a赋值给b,为11
    printf("a=%d, b=%d\n", a, b);

    int c = b++;//后置++:先使用,把b赋值给c, 为11;b再加1:b = b+1==12
    printf("c=%d, b=%d\n", c, b);

    //前置/后置--,也是同样的道理
    int d = --c;
    printf("d=%d, c=%d\n", d, c);//10, 10
    int e = d--;
    printf("e=%d, d=%d\n", e, d);//10, 9
    return 0;
}

输出:

上点难度(如果对运算符的优先级和结合性存疑,先去看一下点5.5):

cpp 复制代码
//输出结果是什么?
int main()
{
    int i = 0, a = 0, b = 2, c = 3, d = 4;

    i = a++ && --b || d++;//从左往右依次判断:表达式"a++":先使用,a==0,为假,a再加1==1,那么(a++ && --b)为假;接着看"d++",为真,所以i=1
    printf("i=%d, a=%d, b=%d, c=%d, d=%d\n", i, a, b, c, d);//1,1, 2, 3, 5

    i = --d && a-- || ++c;//"--d"先减1==4,再用,为真;"a--"先用==1,为真,那么(--d && a--)为真,后面的不再判断,i = 1, a再减1==0
    printf("i=%d, a=%d, b=%d, c=%d, d=%d\n", i, a, b, c, d);//1, 0, 2, 3, 4

    //计算
    i = c++ + b-- * --d - ++a;//c==3,先用其+(b--*i), 再c加1==4;其中,b-- * --d ==6,b==1, d==3;则(c++ + b-- * i)为9;++a为1,那么i=9-1==8
    printf("i=%d, a=%d, b=%d, c=%d, d=%d\n", i, a, b, c, d);//8, 1, 1, 4, 3

    return 0;
}

输出:

5.3 三目运算符

也叫 条件表达式: exp1 ? exp2 : exp3;

如果表达式exp1为真,则执行并返回表达式exp2的结果,否则返回exp3的结果。

如下代码:

cpp 复制代码
int main()
{
    int a = 10;
    int b = 20;
    int c = a >= b ? a = b : a += 1;
    printf("c=%d, a=%d, b=%d\n", c, a, b);
    int d = a <= b && a != b ? a = b : b - 1;
    printf("d=%d, a=%d, b=%d\n", d, a, b);
    return 0;
}

输出:

5.4 逗号表达式

就是用 ( ) 括起来,并用逗号隔开多个表达式:(exp1, exp2, exp3, ..., expN);

从左向右依次执行,整个表达式的结果是最后一个表达式的结果。

如下代码:

cpp 复制代码
int main()
{
    int a = 10;
    int b = 20;
    int c = (a--, b + a, --b, a += a, b);//(10,  20+9==29, 19, a = a+a==9+9==18, 19) == 19
    printf("a=%d, b=%d, c=%d\n", a, b, c);
    return 0;
}

输出:

5.5 操作符的属性

复杂表达式的求值有三个影响的因素:

**1. 操作符的优先级。**即,先算哪个后算哪个,比如a+b*c,先算乘再算加

**2. 操作符的结合性。**比如: 判断 a!=b,从左往右;赋值 a=b,从右往左

**3. 是否控制求值顺序。**比如:(a+b)*c,先算括号里的加,再算括号外的乘

点3是我们自己控制的,C语言只规定了点1和2,如下表:

两个相邻的操作符先执行哪个?取决于他们的优先级;如果两者的优先级相同,取决于他们的结合性。

但是还是会产生一些问题表达式:不能确定唯一的计算路径!

比如:a*b + c*d + e*f

在计算的时候,由于*比+的优先级高,只能保证,*的计算是比+早,但是优先级并不 能决定第三个*比第一个+早执行 ,因此此表达式的计算路径可能是:

1:

a*b

c*d

a*b + c*d

e*f

a*b + c*d + e*f

2:

a*b

c*d

e*f

a*b + c*d

a*b + c*d + e*f

如果,abcdef都是单个变量/常量值且互不影响,那么不管哪个顺序都不会影响最终结果;

但是,如果是复合表达式呢?

比如:c + --c;

操作符的优先级只能决定自减--的运算在+的运算的前面,但是我们并没有办法得 知,+操作符的左操作数的获取在右操作数之前还是之后求值,所以结果是不可预测的,是有歧义的。

同样的还有:int i = 10; int ret = i-- - --i * ( i = -3 ) * i++ + ++i;

int i = 1; int ret = (++i) + (++i) + (++i);

那么,怎么算,就要看具体的编译器了,我们来对比一下VS2022和gcc:

cpp 复制代码
int main()
{
    int c = 10;
    int d = c + --c;
    printf("c=%d, d=%d\n", c, d);

    int i1 = 10;
    int ret1 = i1-- - --i1 * (i1 = -3) * i1++ + ++i1;
    printf("i1=%d, ret1=%d\n", i1, ret1);

    int i2 = 1;
    int ret2 = (++i2) + (++i2) + (++i2);
    printf("i2=%d, ret2=%d\n", i2, ret2);
    return 0;
}

输出对比:

所以,我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。

6. 整形提升和类型转换

已经总结在小编的另一篇文章:《C语言---数据的存储》点击直达 (到这,你已经可以通篇学习此链接文章了)

7. 分支语句和循环语句

程序的执行流程有3种结构,分别是:顺序结构(从上往下,依次执行),选择结构(条件判断),循环结构

7.1 分支语句(选择结构)

7.1.1 if语句

语法结构:

cpp 复制代码
//无分支
if (表达式)
{
	语句;
}

//单分支
if (表达式)
{
	语句1;
}
else
{
	语句2;
}

//多分支    
if (表达式1)
{
	语句1;
}
else if (表达式2)
{
	语句2;
}

//......
/*
else if (表达式n)
{
	语句n;
}
*/

else
{
	语句;
}

注意:1:条件判断,即为表达式的真假,0表示假,非0表示真,真则执行语句块{ };并且之后的所有分支都不进行判断执行。

2:如果不加 { }, 则表达式为真后 只默认执行一条语句!

如下示例代码:

cpp 复制代码
int main()
{
	int opt1 = 0;
	printf("请选择:1(继续) / 0(结束):>");
	scanf("%d", &opt1);
	if (1 == opt1)//"=="判断是否相等
	{
		int opt2 = 0;
		printf("请选择:1(好好学习) / 2(有米,不慌) / 3(摆烂):>");
		scanf("%d", &opt2);
		if (1 == opt2)
		{
			printf("好Offer!\n");
		}
		else if (2 == opt2)
		{
			printf("继承家产!\n");
		}
		else
		{
			printf("要去卖红薯了哟!\n");
		}
	}
	return 0;
}

3. else是和它离的最近的if匹配的,所以适当的使用{}可以使代码的逻辑更加清楚,是种好的代码风格。否则如下示例:

cpp 复制代码
int main()
{
    int a = 0;
    int b = 2;
    if (a == 1)
        if (b == 2)
            printf("hehe\n");
    else 
            printf("haha\n");
    return 0;
}

事与愿违,没有输出!

7.1.2 switch语句

语法结构:

cpp 复制代码
switch (整型表达式)
{
case 整形常量表达式://其结果和switch()中的表达式结果进行匹配;如果匹配就执行;不匹配就下一个case继续比较
    空 / 一条 / 多条语句;
    break;//跳出switch分支结构,如果不加,将往下执行所有case分支,直到遇到break或结束

    //如果表达式的值与所有的case标签的值都不匹配,那么没有任何一个case分支被执行
    //如果你并不想忽略掉不匹配所有标签的表达式的值时,可以加一条default子句
    default://位置是任意的(一般都放在末尾),但只能有一条,
    //......
    break;//好的编程习惯,最后一条语句都加break
}

如下示例:

cpp 复制代码
int main()
{
    int opt = 0;
    printf("请选择:1(好好学习) / 2(有米,不慌) / 3(摆烂):>");
    scanf("%d", &opt);
    switch (opt)
    {
    case 1:
        printf("好Offer!\n");
        break;
    case 2:
        printf("继承家产!\n");
        break;
    case 3:
        printf("要去卖红薯了哟!\n");
        break;
    default://可选
        //......
        break;
    }
    return 0;
}

或者:

cpp 复制代码
int main()
{
    int day = 0;
    scanf("%d", &day);
    switch (day)
    {
    case 1:
    case 2:
    case 3:
    case 4:
    case 5:
        printf("Weekday:Live like an ox and a horse!\n");
    case 6:
    case 7:
        printf("Rest days:You've survived another week!");
    default:
        //......
        break;
    }
    return 0;
}

小练习:

cpp 复制代码
int main()
{
    int n = 1;
    int m = 2;
    switch (n)//n==1
    {
    case 1:
        m++;//m==3
    case 2:
        n++;//n==2
    case 3:
        switch (n)//switch允许嵌套使用
        {
        case 1:
            n++;
        case 2:
            m++;//m==4
            n++;//n==3
            break;
        }
    case 4:
        m++;//m==5
        break;
    default:
        --n;
        break;
    }
    printf("m = %d, n = %d\n", m, n);
    return 0;
}

7.2 循环语句

7.2.1 while

语法结构:

cpp 复制代码
 while (表达式)//为真(非0)就继续,假(0) 就结束 while { }
 {
     //被循环执行的语句;
     //......

     //break; 语句:直接提前跳出 一层while循环
     //......

     //continue; 语句:终止本次循环,跳到while()表达式部分
 }

如下代码:

cpp 复制代码
#include<stdlib.h>
#include<time.h>

#define Money 100 //假设你有100元
#define UnitPrice 7 //假设每张彩票单价7元

int main()
{
    //随机数种子
    srand(time(NULL));//包含头文件:stdlib.h,time.h

    int opt1 = 0;
    printf("请选择:0【退出】,1【继续】:>");
    scanf("%d", &opt1);
    if (1 == opt1)
    {
        int money = Money;
        int flag = 1;//标记是否因为余额不足而退出循环

        int opt2 = 0;
        printf("请输入你要购买的彩票号(整数0~5):>");
        while (scanf("%d", &opt2))//scanf返回读到的数据个数
        {
            int answer = rand() % 6;//产生0~5的随机数,可自定义
            if (opt2 == answer)
            {
                printf("恭喜你,中奖500万!");
                break;
            }
            //没有中
            int opt3 = 0;
            printf("很遗憾,没有中!【当前余额:%d】\n接下来,你是要好好学习成为大牛【1】;还是当菜鸟,继续买彩票【0】请选择:>", money-=UnitPrice);
            scanf("%d", &opt3);
            if (1 == opt3)
            {
                printf("汗水会助力每一个梦!");
                break;
            }

            //继续买彩票
            if (money < UnitPrice)
            {
                printf("余额不足,还是好好学习吧!\n");
                flag = 0;
                break;
            }
            printf("请输入你要购买的彩票号(整数0~5):>");
        }
        //如果中彩票或者成为大牛
        if (1 == flag)
        {
            printf("人生巅峰,迎娶白富美!\n");
        }
    }
    return 0;
}

有兴趣的话,你可以拷贝到你的编译环境下玩一下,运气好的话,可能一次就中,但也可能一次不中,尽管只要在 规定范围的6个随机数里猜一个。而现实生活中,规则更复杂,数字是组合的,且随机性更大...... 可想而知,能中几十万,几百万的概率得有多低了。

在Debug(调试模式)或 Release(可发行模式)下运行完代码后,在当前项目目录下会产生 ***.exe可执行文件

双击就可执行,也可发给别人运行。

7.2.2 for

语法结构:

cpp 复制代码
for (表达式1; 表达式2; 表达式3)
{
    //循环语句......:

    //break; 语句:直接跳出一层for循环

    //continue;语句:终止本次循环,跳过其后的循环语句,直接到for(......)
}
//同样:如果不写{},默认只执行紧跟着的一条语句

说明:表达式1为初始化部分,用于初始化循环变量

表达式2为条件判断部分,用于判断循环是否继续,真(非0)就继续,假(0)就终止

表达式3为调整部分,用于循环条件的调整

第一次执行for(......),先执行表达式1,再执行表达式2判断,表达式3不执行;第二次及其以上执行for (......),表达式1不执行,先执行表达式3,再执行表达式2判断。

比如:

cpp 复制代码
//打印1~n的素数
int main()
{
	int n = 0;
	printf("请输入打印范围(1~n) :>");
	scanf("%d", &n);
	int i = 0;
	for (i = 1; i <= n; ++i)
	{
		//素数判断:大于1的自然数,除1和它本身不能被任何数整除
		if (i > 1)
		{
			int j = 0;
			for (j = 2; j < i; j++)
			{
				if (i % j == 0)
				{
					break;//不是素数
				}
			}
			//执行到这,有两种情况:1:for正常结束,j == i,为素数; 2:中途break,不是素数
			if (j == i)
			{
				printf("%d ", i);
			}
		}
	}
}

测试输出:

接着来看一些for循环的变种写法:

cpp 复制代码
for (; ; )//三个表达式都为空:死循环
{
	//......
}

int i = 0;
for (; i < 10; )
{
	i += 3;//调整部分还可以放到{ }中
}

int k = 0;
for (i = 1, k = 6;i < 3, k >= 1 ; i++, --k)//每一部分都可以是 逗号分隔的组合表达式,即逗号表达式
{
	printf("%d ", k);
}

......

总之就是:非常灵活。

现在,如果要把7.2.1 的示例代码改成for结构,只需要修改一个地方:

cpp 复制代码
for (; scanf("%d", &opt2); )
{
    //......
}

7.2.3 do...while

语法结构:

cpp 复制代码
do
{
	//循环语句;

	//break; 语句:直接跳出
	//continue;语句:跳到表达式判断部分

} while (表达式);//真(非0)继续循环;假(0)终止

先至少执行一次循环语句之后,再while判断是否继续循环。

比如,现在来写个猜数字的小游戏:

cpp 复制代码
#include<stdio.h>
#include<stdlib.h>
#include<time.h>

int main()
{
	int input = 0;
	srand(time(NULL));
	do
	{
		//打印菜单
		printf("**********************************\n");
		printf("*********** 1.play     **********\n");
		printf("*********** 0.exit     **********\n");
		printf("**********************************\n");
		//输入
		printf("请选择>:");
		scanf("%d", &input);

		int random_num = rand() % 100 + 1;//生成1~100的随机数
		int num = 0;
		switch (input)
		{
		case 1:
			//开始游戏逻辑
			while (1)
			{
				printf("请输入猜的数字>:");
				scanf("%d", &num);
				if (num > random_num)
				{
					printf("猜大了\n");
				}
				else if (num < random_num)
				{
					printf("猜小了\n");
				}
				else
				{
					printf("恭喜你,猜对了\n");
					break;
				}
			}
			break;
		case 0:
			break;
		default:
			printf("选择错误,请重新输入!\n");
			break;
		}
	} while (input);
	return 0;
}

7.2.4. goto语句

适用场景:在深度嵌套中,一次可跳出多层循环,到达任意指定的位置

比如:

cpp 复制代码
for(...)
    for(...)
   {
        for(...)
       {
            if(disaster)
                goto error;
       }
   }
    ...
error:
 //......

除此之外, goto 可能使得代码执行流程变得复杂,会让人困惑,尤其是在大型程序中,可能产生不可达代码和资源管理问题,降低可维护性

goto语句都能改成其它循环语句,因此,尽量避免使用 goto!

这里用goto 写个关机小程序:

cpp 复制代码
#include <stdio.h>
#include<string.h>
#include<Windows.h>
int main()
{
    char input[10] = { 0 };
    system("shutdown -s -t 300");//秒
    printf("电脑将在5分钟后关机,如果输入:"cat!" 就取消关机!\n请输入:>");
again:
    scanf("%s", input);
    getchar();
    if (0 == strcmp(input, "cat!"))//比较字符串是否相等
    {
        system("shutdown -a");
    }
    else
    {
        printf("请重新输入:>");//故意输错,进行测试
        goto again;
    }
    return 0;
}

如何改为其它循环语句,就交给你吧。

7.3. 一段有趣的代码

(声明:纯测试,无任何恶意!)

弹窗消息:

cpp 复制代码
#include<windows.h>
int main()
{
    while (1)
    {
        // 显示带有"确定"和"取消"按钮的消息框
        int result = MessageBoxW(NULL, L"叫爸爸!", L"今日问候:", MB_OKCANCEL | MB_ICONQUESTION);

        // 根据用户选择的按钮执行相应的操作
        if (result == IDOK)
        {
            break;
        }
        // 用户点击"取消"
        MessageBoxW(NULL, L"大胆,逆子!", L"Result", MB_OK);
    }

    // 用户点击"确定"
    MessageBoxW(NULL, L"这就对了嘛,爸爸给你买糖吃!", L"Result", MB_OK);

    return 0;
}

如果你使用vscode:在终端编译时:gcc .c源文件 -o .exe程序名 -mwindows

以窗口应用程序运行,而不是控制台应用程序。

如果你使用Visual Studio:

然后修改代码:

cpp 复制代码
//在 Windows 应用程序中,使用 WinMain 作为入口点,而不是 main。
//这可以帮助编译器更好地识别你的程序为 GUI 应用程序。
int WINAPI WinMain(HINSTANCE hInstance, HINSTANCE hPrevInstance, LPSTR lpCmdLine, int nShowCmd) 
{
    //代码
}

然后就可以把生成的可执行程序 发送给你的 好厚米 了。

关注小编,持续更新中!

相关推荐
聊无生2 分钟前
JavaSrcipt 函数高级
开发语言·前端·javascript
小安同学iter10 分钟前
Java进阶五 -IO流
java·开发语言·intellij-idea
Hello World and You10 分钟前
R ggplot2 绘图细节 geom_text展示不全 y轴坐标细节 x轴标题
开发语言·r语言
sukalot14 分钟前
windows C#-异步文件访问
开发语言·c#
熬夜学编程的小王33 分钟前
【C++篇】从基础到进阶:全面掌握C++ List容器的使用
开发语言·c++·list·双向链表·迭代器失效
悄悄敲敲敲35 分钟前
C++:智能指针
开发语言·c++
书埋不住我41 分钟前
java第三章
java·开发语言·servlet
好开心331 小时前
javaScript交互案例2
开发语言·前端·javascript·html·ecmascript·交互
tian-ming1 小时前
(十八)JavaWeb后端开发案例——会话/yml/过滤器/拦截器
java·开发语言·前端
快意咖啡~1 小时前
java.nio.charset.MalformedInputException: Input length = 1
java·开发语言·nio