目录
一.学习的第一个C程序
1.hello world
#include <stdio.h>
int main()
{
printf("hello world\n");
return 0;
}
在C语言中,`scanf`和`printf`是两个常用的标准输入输出函数,分别用于从标准输入设备(通常是键盘)读取输入和向标准输出设备(通常是显示器)输出数据。
1. `printf`函数:
- `printf`函数用于向标准输出设备输出格式化字符串。它的基本语法是:`int printf(const char format, ...)`。
- `printf`函数可以输出各种数据类型,如整数、浮点数、字符、字符串等,以指定的格式显示在屏幕上。
- 例如,`printf("Hello, World!");`将在屏幕上输出字符串"Hello, World!"。
2. `scanf`函数:
- `scanf`函数用于从标准输入设备读取输入并将其存储到变量中。它的基本语法是:`int scanf(const char *format, ...)`。
- `scanf`函数通常与`printf`函数搭配使用,用于接收用户输入的数据。
- 例如,`int num; scanf("%d", &num);`将等待用户在控制台输入一个整数,并将输入的整数存储在变量`num`中。
需要注意的是,`printf`和`scanf`函数的第一个参数是格式化字符串,用于描述输出或输入数据的格式,后面的参数是变量地址(对于`scanf`来说),用于存储输入的数据。
这两个函数在C语言中非常常用,能够实现基本的输入输出操作,是学习和编写C程序时必不可少的工具。
二.计算机中的数据存储
计算机中的存储分为数值型数值的存储和非数值型数据的存储两大部分
1.数值型数据的存储
1.1 二进制
前导符 0b
二进制是一种计数系统,使用只有两个数字0和1的数制。在计算机编程和数据处理领域中,二进制是最基本的数据表示方式。
在二进制系统中,每个位只能表示0或1。二进制数是按权展开的,例如,一个二进制数"1101"表示为:
(1 * 2^3) + (1 * 2^2) + (0 * 2^1) + (1 * 2^0) = 13
二进制和十进制之间的转换是常见的操作,下面介绍两种转换方法:
- 二进制转十进制:从二进制数的最右边开始,依次乘以2的指数次方,然后相加即可得到十进制数。例如,将二进制数"1101"转换为十进制数:
(1 * 2^3) + (1 * 2^2) + (0 * 2^1) + (1 * 2^0) = 13
反向排列余数得到二进制数"1101"。
这种二进制和十进制之间的转换方法可以帮助理解计算机内部数据处理的过程,同时也是编程中常用的操作之一。
1.2 十进制
没有前导符
十进制是最常见的计数系统,也称为基数为10的十进制数系统。在十进制系统中,数字由0到9组成
十进制转二进制:使用"除2取余"的方法,将十进制数不断除以2,逐步得到余数,然后反向排列这些余数即可得到对应的二进制数。例如,将十进制数13转换为二进制数:
13 / 2 = 6 余1 6 / 2 = 3 余0 3 / 2 = 1 余1 1 / 2 = 0 余1
反向排列余数得到二进制数"1101"。
1.3 八进制
前导符 0
八进制是一种基数为8的计数系统,也称为八进制数制。在八进制系统中,数字由0到7组成,每个位上的数字表示对应权值的8的幂次方。
与十进制类似,八进制数也可以表示各种数量和数值。例如,一个八进制数"1256"表示为:
(1 * 8^3) + (2 * 8^2) + (5 * 8^1) + (6 * 8^0) = 512 + 128 + 40 + 6 = 686
八进制转二进制
方式1 八转十 十转二
方式2 一位八进制对应三位二进制
1.4 十六进制
前导符 0x
十六进制是一种基数为16的计数系统,也称为十六进制数制。在十六进制系统中,除了0到9这10个数字外,还包括A到F这6个字母,分别表示10到15这些数字。
与十进制类似,十六进制数也可以表示各种数量和数值。例如,一个十六进制数"1A3F"表示为:
(1 * 16^3) + (10 * 16^2) + (3 * 16^1) + (15 * 16^0) = 4096 + 2560 + 48 + 15 = 6719
在计算机科学和信息技术领域,十六进制经常用于表示内存地址、颜色代码、字符编码等信息,因为它可以更紧凑地表示大量数据。另外,十六进制与二进制之间的转换也比较容易,因为十六进制的每一位可以用4个二进制位表示。
下面是一个各种进制显示的代码自己试试
#include <stdio.h>
int main()
{
//定义一个整形变量,赋值一个二进制的数
int value1 =0b1010001;
printf("value1 = %d\n",value1);//%d 是十进制的占位符
printf("value1 = %#o\n",value1);//%o 是八进制占位符 #输出前导符
printf("value1 = %#x\n",value1);//%x 是十六进制占位符
printf("------------------\n");
//定义一个整形变量,赋值一个十进制数字
int value2 =1314;
printf("value2 = %d\n",value2);
printf("value2 = %#o\n",value2);
printf("value2 = %#x\n",value2);
printf("------------------\n");
//定义一个整形变量,赋值一个八进制数字
int value3 =0345;
printf("value3 = %d\n",value3);
printf("value3 = %#o\n",value3);
printf("value3 = %#x\n",value3);
printf("------------------\n");
//定义一个整形变量,赋值一个十六进制数字
int value4 =0x78cd;
printf("value4 = %d\n",value4);
printf("value4 = %#o\n",value4);
printf("value4 = %#x\n",value4);
return 0;
}
2.非数值型数据的存储
计算机只能识别二进制的数值型数据,但是在编程的过程中,有些场合需要用到非数值型的数据如网址,人名,企业名,所以有了ASCII码。
man ascii 可惜查询ASCII码
在编程时遇见的所有被''和""引起来的内容都是非数值型数据
'm' '0' "www.baidu.com"
常见ASCII码
b ------ 98
B ------ 66
0 ------ 48
\n ------ 10
\0 ------ 0
转义字符:
所有的ascii码都可以用'\'加上一个数字(八进制)来表示
在c语言中还定义了一些'\'加上字符来表示一些没法显示的字符如
'\n' 't' 'a' '0'这些字符全是转义字符
三.词法符号
1.关键字
关键字就是已经定义好的的一些单词,可以直接使用,注意关键字都是小写的
- char: 用于声明字符型变量。
- short: 用于声明短整型变量。
- int: 用于声明整型变量。
- long: 用于声明长整型变量
- float: 用于声明单精度浮点型变量。
- double: 用于声明双精度浮点型变量。
- enum: 用于定义枚举类型。
- extern: 声明外部变量或函数。
- struct: 定义结构体。
- union: 定义联合体。
- signed 表示有符号整数,即可以表示正数、负数和零。
- unsigned 则表示无符号整数,只能表示非负数(包括正数和零),而不能表示负数。无符号整数的取值范围相对较大,因为不需要一个符号位来表示正负
- const: 用于定义常量,其值不能被修改。
- static: 声明静态变量或函数。
- typedef: 定义新的数据类型。。
- volatile: 标记变量可以被异步修改。
- register: 声明寄存器变量,提高访问速度。
- auto: 用于声明自动变量,存储在函数作用域内。
- sizeof: 返回变量的大小。
- if else: 条件语句的关键字,根据条件执行相应的代码块
- switch: 用于多分支选择。
- case: 在switch语句中用于标签。
- default: 在switch语句中的默认标签。
- goto: 控制程序流程的跳转。
- while: 循环结构的关键字,执行循环直到条件为假。
- do: 循环结构的关键字,执行循环体直到条件为假。
- for: 循环结构的关键字,控制循环次数。
- break: 在循环或switch语句中使用,用于跳出循环或switch语句。
- continue: 结束当前循环的迭代,继续下一次迭代。
- return: 从函数中返回值
2.标识符
标识符是编程语言中用于标识变量、函数、常量等各种程序实体的名称的符号序列。在大多数编程语言中,标识符的命名规则通常包括以下几点:
1. 标识符可以由字母、数字和下划线组成,但必须以字母或下划线开头。
2. 标识符通常区分大小写,因此大小写字母不同的标识符被视为不同的标识符。
3. 标识符不能使用语言的关键字或保留字,以免造成冲突。
在编程中,合理命名规范的标识符有助于提高代码的可读性和可维护性,建议遵循驼峰命名法或下划线命名法等命名规范,以规范命名各种程序实体并提高代码质量。
四.数据类型
1.c语言的本质
1.1.操作内存
内存和硬盘的有什么区别:
内存和硬盘是计算机系统中两种不同类型的存储设备,它们在功能、速度、作用以及使用方式等方面有着显著的区别。
1. 功能:
- 内存(RAM)是一种临时存储设备,用于存储正在运行的程序和数据。内存中的数据可以被 CPU 快速访问,但在断电后数据会丢失。
- 硬盘(HDD或SSD)是一种永久性存储设备,用于长期存储文件、程序以及操作系统等数据。硬盘中的数据在断电后仍能保持。SSD相比HDD有更快的读写速度。
2. 速度:
- 内存的读写速度比硬盘快得多。CPU可以直接访问内存中的数据,因此内存速度对系统性能有显著影响。
- 硬盘的读写速度相对较慢。即使是SSD也比内存慢很多,但SSD相比HDD有更快的速度。
3. 作用:
- 内存主要用于存储正在运行的程序、操作系统和暂时需要的数据。它直接影响到计算机的运行速度和多任务处理能力。
- 硬盘用于长期存储文件、程序和其他数据,包括操作系统本身。硬盘的容量通常比内存大得多。
4. 使用方式:
- 内存是计算机的临时存储,数据在计算机关闭或断电后会丢失,所以通常只用于存储临时数据。
- 硬盘是计算机的永久性存储,数据不会因为关机而丢失,因此用于存储长期数据和程序。
总的来说,内存和硬盘在存储功能、速度和使用方式上有明显的区别。内存用于临时存储正在运行的程序和数据,速度快但数据不稳定;硬盘用于永久性存储文件和数据,速度慢但数据稳定。在计算机系统中,二者通常共同工作,互补彼此的功能,提供了完整的存储解决方案。
1.2.RAM,ROM,FLASH
RAM(随机存取存储器)、ROM(只读存储器)和Flash存储器是计算机系统中常见的存储设备,它们之间存在着以下区别:
1. RAM(随机存取存储器):
- RAM是一种易失性存储设备,数据在断电后会丢失。
- RAM用于存储正在运行的程序、操作系统和临时数据,CPU可以随机访问RAM中的数据。
- RAM的读写速度快,是计算机性能的关键因素之一。
- RAM容量一般比较小,但速度快。
2. ROM(只读存储器):
- ROM是一种非易失性存储设备,数据在断电后不会丢失。
- ROM通常用于存储固化的程序或数据,如引导程序(BIOS)或其他固定信息,用户一般无法修改。
- ROM的数据只能被读取,无法被写入或修改。
- ROM的容量通常较小,但稳定且可靠。
3. Flash存储器:
- Flash存储器是一种闪存技术,结合了RAM和ROM的特性,具有读写速度快和数据稳定的优点。
- Flash存储器可以被多次擦写和重写,类似于硬盘,常用于存储操作系统、文件和应用程序等数据。
- 在移动设备(如手机、平板电脑)和存储设备(如U盘、固态硬盘)中广泛使用Flash存储器。
- Flash存储器也分为NAND Flash和NOR Flash两种类型,用途和特性略有区别。
总的来说,RAM用于临时存储正在运行的程序和数据,速度快但容量小且易失;ROM用于存储固定的程序或数据,稳定但容量小且只读;Flash存储器则结合了两者的优点,具有较快的读写速度和稳定的数据保存能力,广泛应用于各种电子设备中。
2.数据类型的作用和分类
2.1.数据类型的作用
数据类型相当于一个模子,规定了他定义的变量,需要占用的多大的内存空间
数据类型在编程中的作用主要可以简单概括为以下几点:
- 决定变量所占用的内存空间大小。
- 规定数据的类型和含义,如整数、浮点数、字符等。
- 约束数据操作的规范性和有效性。
- 可以进行类型检查,避免类型错误。
- 选择合适的数据类型可以提高程序的执行效率。
2.2.整数类型
2.21 char类型
char
类型在不同编程语言中的占用字节、范围以及有符号和无符号的范围可能会有所不同。以下是一般情况下char
类型的一些特点:
- 占用字节:通常情况下,
char
类型占用一个字节(8位)的内存空间。 - 范围:
char
类型的范围取决于是有符号还是无符号。对于有符号的char
,通常范围为 -128 到 127(signed char),而对于无符号的char
,范围为 0 到 255(unsigned char)。 - 有符号和无符号的范围:一般情况下,有符号的
char
类型范围为 -128 到 127,而无符号的char
类型范围为 0 到 255。这是由于有符号char
使用一个位来表示符号位(0 为正,1 为负),因此有符号char
的有效范围减半。
在使用 char
类型时,应根据具体语言的规范和编译器的实现来确定其占用字节、范围以及有符号和无符号的差异。
2.22 short类型
short
类型通常用来表示整数数据,并且通常比 int
类型占用更少的内存空间。以下是关于 short
类型的一些常见信息:
- 占用字节:通常情况下,
short
类型占用的字节数为2个字节(16位),这使得其在内存中占用的空间比int
类型少。 - 范围:
short
类型的范围取决于是有符号还是无符号。对于有符号的short
,通常范围为 -32768 到 32767,而对于无符号的short
,范围为 0 到 65535。 - 有符号和无符号的范围:和
char
类型类似,有符号的short
类型和无符号的short
类型存在范围上的差异,有符号类型的范围是对称的,而无符号类型的范围则从0开始。
short
类型通常用于需要节省内存空间的情况下,但需要注意的是,由于其范围较小,可能在某些情况下无法容纳较大的整数值。在实际编程中,需要根据具体需求和数据范围来选择合适的数据类型。
2.23 int类型
int
类型通常用于表示整数,其大小取决于编程语言和平台。在大多数现代编程语言和平台中(在不同位数的操作环境不同),int
类型通常占用 4 个字节,并且可以分为有符号和无符号两种类型。
有符号int类型:
- 占用字节:4 字节
- 表示范围:-2^31 (-2,147,483,648) 到 2^31-1 (2,147,483,647)
无符号int类型:
- 占用字节:4 字节
- 表示范围:0 到 2^32-1 (4,294,967,295)
需要注意的是,int
类型的具体大小可以因编程语言、平台或编译器的不同而有所变化。在一些编程语言中,int
类型可能占用 2 个字节(16 位),因此,在编写代码时,建议查阅相关文档以确定具体的 int
类型大小。
2.24 long类型
`long` 类型通常用于表示整数,其大小也取决于编程语言和平台。在大多数编程语言中,`long` 类型通常占用 4 字节或 8 字节(32 位或 64 位),并且也可以分为有符号和无符号两种类型。
有符号long类型:
- 占用字节:4 字节或 8 字节
- 表示范围:-2^31 (-2,147,483,648) 到 2^31-1 (2,147,483,647) 或 -2^63 到 2^63-1 (64 位)
无符号long类型:
- 占用字节:4 字节或 8 字节
- 表示范围:0 到 2^32-1 (4,294,967,295) 或 0 到 2^64-1
与`int`类型相比,`long`类型可以表示更大范围的整数值。在需要处理较大整数时,通常会选择使用`long`类型来确保可以覆盖所需的取值范围。需要注意的是,`long`类型的具体大小也会因编程语言、平台或编译器的不同而有所变化,因此在编写代码时最好查阅相关文档以确认具体的`long`类型大小。
2.25 long long类型
`long long` 类型是 C 和 C++ 编程语言中提供的另一种整数数据类型,通常用于表示更大范围的整数值。与 `long` 类型相比,`long long` 类型通常更大,允许表示更广范围的整数值。
有符号 long long 类型:
- 占用字节:8 字节
- 表示范围:-2^63 (-9,223,372,036,854,775,808) 到 2^63-1 (9,223,372,036,854,775,807)
无符号 long long 类型:
- 占用字节:8 字节
- 表示范围:0 到 2^64-1 (18,446,744,073,709,551,615)
`long long` 类型一般用于需要处理非常大整数值或需要确保整数范围足够大的情况。在编写需要处理大整数计算或表示大范围整数值的程序时,`long long` 类型通常会更适合。需要注意的是,`long long` 类型的大小也取决于编程语言、平台或编译器的具体实现,因此在编写代码时最好查阅相关文档以确认具体的 `long long` 类型大小。
2.3.浮点类型
2.3.1 float类型
在大多数编程语言中,float类型通常占用4个字节(32位)的存储空间,用于存储单精度浮点数。这种数据类型可以表示大约7位有效数字,并且在表示小数时具有一定的精度。
2.3.2 double类型
对于双精度浮点数(double类型),通常占用8个字节(64位)的存储空间,提供更高的精度,可以表示大约15到16位有效数字。不同的编程语言和底层系统可能会有细微的差异,但通常情况下,float类型占用4个字节,double类型占用8个字节。
2.4.空类型
void 就是空类型
3.原码,反码,补码
输在存储的时候,涉及到原码反码补码转化问题
原码---------是给人看的
反码---------是用来做原码和补码转换的
补码---------是给计算机看的,计算机中存储的都是数据的补码
无符号数:没有负数 原码 反码 补码 一致
有符号正数: 符号位为0 原码 反码 补码 一致
有符号负数: 符号位为1 需要特殊转换
反码 = 原码符号位不变,数据位按位取反(1变0,0变1)
补码 = 反码加1
方法: 存储时看数据(正负),取出时看类型(有无符号)
-128 规定就是1000 0000不要深究
#include <stdio.h>
int main()
{
char a = 10;
printf("%d\n",a);
//存储时
//原码:0000 1010
//反码:0000 1010
//补码:0000 1010
//取出时
//补码:0000 1010
//反码:0000 1010
//原码:0000 1010
char b = -10;
printf("%d\n",b);
//存储时
//原码:1000 1010
//反码:1111 0101
//补码:1111 0110
//取出时
//补码:1111 0110
//反码:1111 0101
//原码: 1000 1010
char c = 129;
printf("%d\n",c);
//存储时
//原码:1000 0001
//反码:1000 0001
//补码:1000 0001
//取出时
//补码:1000 0001
//反码:1000 0000
//原码:1111 1111等于-127
return 0;
}
五.常量和变量
1.各种类型常量
在C语言中,常量的占位符(格式说明符)用于在`printf`和`scanf`等函数中指定输出或输入的格式。下面列出了常用的常量类型及其对应的占位符:
1. 整型常量:
- 十进制整数:`%d`
- 八进制整数:`%o`
- 十六进制整数(小写字母):`%x`
2. 浮点型常量:
- 十进制浮点数:`%f`
- 科学计数法表示的浮点数:`%e` 或 `%E`
- 无论小数部分有多少位都输出:`%g`
- 固定小数位数输出:`%f`
3. 字符常量:
- 字符:`%c`
4. 字符串常量:
- 字符串:`%s`
如果是printf("%c\n",ch1);作用相当于\t
5. 枚举常量:一般使用整型常量对应的占位符。
6. 宏常量:宏常量在C语言中并没有特定的占位符。
7. 特殊常量:
- `NULL`:一般用`%p`占位符打印空指针的值。
- `true`和`false`:可以使用`%d`打印`true`或`false`的整数值。
这些占位符用于`printf`函数中输出各种类型的数据,而在`scanf`函数中,相应的占位符用于指定输入数据的格式。
2.变量
2.1.概念:在程序运行的过程中,值允许变化的量
变量是用来保存数据的
变量的大小:由定义变量的类型决定
定义变量的格式:
存储类型 数据类型 变量名
其中存储类型:
const static extern volatile register auto
变量名要符合标识符命名规范
2.2.变量的初始化和赋值
在C语言中,变量的初始化和赋值是两个不同的操作。变量的初始化是在声明变量的同时为其赋初始值,而变量的赋值是在变量已经声明后给其赋新的值。
示例代码如下:
#include <stdio.h>
int main() {
// 初始化变量
int num1 = 10;
// 赋值操作
int num2;
num2 = 20;
printf("num1: %d\n", num1);
printf("num2: %d\n", num2);
return 0;
}
在上面的示例中,变量num1
在声明的同时初始化为10,而变量num2
先声明后赋值为20
2.3.使用变量的注意事项
1.变量如果没有定义,不能直接使用
2. 定义的变量的时候,如果不知道用什么初始化可以用,否则里面是随机值
3.变量是可以参与运算的
int num1 = 0;
int num2 = num1 + 10;
num2 = num2 + 100;
4.一行中可以有多个变量,中间用逗号隔开
int a = 0, b = 0 ,c = 0;
5. 在同一个作用域里不允许定义重名的变量 作用域 就是{}内不能重复出现
2.4.强制类型转换
注意:强制类型转换是不安全的,小转大还好,大转小可能会出现数据的截断和丢失
所谓强制类型转换,就是在某次运算中使用某种方式将变量的值的类型转换成其他数据类型来参与运算,但是变量本身的类型及值都不会发生改变
装换方式分为 显式强转 和隐式强转
显示强转
隐式强转
隐式强转是由编译器根据上下文自动推导的,如果编译器认为安全则允许使用,不安全会报一个警告,取决于编译器严谨程度
注:有符号数和无符号数参与运算时候,会将有符号数转换成无符号数
六.运算符
在C语言中,运算符是用于执行各种操作的特殊符号。C语言中的运算符可以分为多种类型,包括算术运算符、关系运算符、逻辑运算符、赋值运算符、位运算符、三元运算符等。
1.运算符分类
1.1 算术运算符:
- 加法:+
- 减法:-
- 乘法:*
- 除法:/
- 取模:% 左右必须是整数
1.2. 关系运算符:
- 等于:==
- 不等于:!=
- 大于:>
- 小于:<
注:
在C语言中,条件表达式100 <= num1 <= 999
并不能按照你想要的方式来判断num1
是否为一个三位数。这是因为C语言中不支持连续比较操作符(<=
)进行多次链式比较。
实际上,100 <= num1 <= 999
的判断过程是这样的:
- 首先,
100 <= num1
会返回一个布尔值,即true
或false
。 - 然后,该布尔值(
true
或false
)会被转换为对应的整数值,即1
(true
)或0
(false
)。 - 最后,
1 <= 999
或0 <= 999
总是为真,因此整个条件表达式总是为真。
因此,无论你输入什么值,包括1234,都会导致程序运行if
语句块中的代码。
要想正确判断num1
是否为一个三位数,你可以修改条件表达式为 num1 >= 100 && num1 <= 999
这样才能实现你预期的判断逻辑。
1.3. 逻辑运算符:
- 与:&&
- 或:||
- 非:!
在C语言中,逻辑运算符用于对多个条件进行逻辑运算,常见的逻辑运算符包括:
- `&&`:逻辑与(AND),当两个条件同时为真时,结果为真。
- `||`:逻辑或(OR),当两个条件至少有一个为真时,结果为真。
- `!`:逻辑非(NOT),对条件取反,如果条件为真则结果为假,如果条件为假则结果为真。
逻辑与(&&)逻辑与运算符用于两个条件同时成立时才为真。例如:
#include <stdio.h>
int main()
{
int x = 5;
int y = 10;
if (x > 0 && y < 20) {
printf("x大于0,并且y小于20\n");
}
}
只有当 `x` 大于0且 `y` 小于20时,才会执行 `printf` 语句。
逻辑或(||)逻辑或运算符用于两个条件至少有一个成立时为真。例如:
#include <stdio,h>
int main()
{
int age = 25;
if (age < 18 || age > 60) {
printf("年龄小于18或者大于60\n");
}
}
只要 `age` 小于18或者大于60,就会执行 `printf` 语句。
逻辑非(!)
逻辑非运算符用于对条件取反。例如:
#include <stdio.h>
int main()
{
int flag = 0;
if (!flag) {
printf("flag为假\n");
}
}
在上面的例子中,只有当 `flag` 的值为0时,`!flag` 的值为真,会执行 `printf` 语句。
逻辑运算符通常与 `if` 语句一起使用,用于控制程序的流程。正确使用逻辑运算符可以帮助我们编写更加灵活和复杂的逻辑判断条件。
注:逻辑运算符的短路原则,逻辑运算符的短路原则指的是在进行逻辑运算时,如果根据左侧的条件已经可以确定整个表达式的结果,就不再计算右侧的条件。这种行为可以提高程序的效率,并且在一些情况下可以避免不必要的计算。
1.4. 赋值运算符:
- 赋值:=
- 加法赋值:+=
- 减法赋值:-=
- 乘法赋值:*=
普通赋值运算符 =
int x;
x = 10;
在上面的例子中,将整数值 10
赋给变量 x
。
复合赋值运算符
-
+=
:加法赋值运算符
int y = 5;
y += 3; // 等价于 y = y + 3;
在上面的例子中,将变量 y
的值加上 3
,然后再赋给 y
。
-
-=
:减法赋值运算符
int z = 8;
z -= 2; // 等价于 z = z - 2;
在上面的例子中,将变量 z
的值减去 2
,然后再赋给 z
。
-
*=
:乘法赋值运算符
int a = 4;
a *= 5; // 等价于 a = a * 5;
在上面的例子中,将变量 a
的值乘以 5
,然后再赋给 a
。
-
/=
:除法赋值运算符
int b = 20;
b /= 4; // 等价于 b = b / 4;
在上面的例子中,将变量 b
的值除以 4
,然后再赋给 b
。
赋值运算符在程序中经常用到,可以简化代码,并且方便对变量进行更新和操作。
1.5. 位运算符:
- 按位与:& 对应位上的两个操作数都为1时,结果为1,否则为0。
- 按位或:| 对应位上的两个操作数只要有一个为1,结果即为1,否则为0。
- 按位取反:~ 操作数的每一位取反,即0变为1,1变为0。
- 按位异或:^ 两个操作数不同时为1时,否则为0。
- 左移:<< 操作数的二进制表示向左移动指定的位数,右侧用0填充。
- 右移:>> 操作数的二进制表示向右移动指定的位数,左侧用符号位填充(对于有符号数)或者用0填充(对于无符号数)。
至于结果有一个0xfffffffa在C语言中,unsigned int
的位数通常是32位(取决于编译器和系统架构,但现代系统普遍为32位或64位)。代码中 ~a
的按位取反操作会作用于所有二进制位,而不仅仅是低8位。以下是关键分析:
1. 数值的二进制表示
-
a = 5
(十进制)的二进制表示:-
32位系统:
00000000 00000000 00000000 00000101
-
取反操作
~a
:11111111 11111111 11111111 11111010
-
对应的十六进制为:
0xFFFFFFFA
-
2. 为什么你期望 0xFA
?
-
0xFA
是二进制11111010
的十六进制形式,对应8位数据。 -
但
unsigned int
是32位类型,取反会生成32位的值,低8位是FA
,高位全部为1
(即0xFFFFFFFA
)。
3. 如何得到 0xFA
?
如果你希望只保留低8位,需要显式截断高位:
c = ~a & 0xFF; // 取反后与0xFF按位与,保留低8位
printf("c = %#x\n", c); // 输出 0xfa
1.6. 条件运算符(三元运算符):
- 表达式1 ? 表达式2 : 表达式3
执行逻辑,如果表达式1为真,则执行表达式2,否则执行表达式3
1.7. 逗号运算符():
- 逗号运算符用于连接两个或多个表达式,求值顺序是从左到右,返回最右边表达式的值。
- 逗号运算符的优先级是最低的,可以用于一条语句中执行多个操作。
- 例如:
int a = 1, b = 2, c = 3;
中的逗号用于分隔不同的变量初始化。
1.8. sizeof运算符
- sizeof运算符用于计算数据类型或变量在内存中的字节大小。
- sizeof运算符返回的是一个
size_t
类型的值,表示字节数。 - 可以用于计算数据类型的大小或变量所占的内存大小。
- 例如:
int size = sizeof(int);
表示获取int
类型的字节数。
1.9.自增自减运算符
在C语言中,自增运算符 ++
和自减运算符 --
是常用的一元运算符,它们可以分别使变量的值加一或减一。这两个运算符可以放在变量的前面(前置形式)或者后面(后置形式)进行使用,其具体效果是与运算符放置的位置相关的。
前置形式:
- 当运算符放在变量前面,如
++num
或--num
,先进行自增或自减操作,然后再引用变量。 - 示例:
int num = 5;
int result = ++num; // 此时 num 变为 6,同时 result 的值也为 6
后置形式:
- 当运算符放在变量后面,如
num++
或num--
,先引用变量的值,然后再进行自增或自减操作。 - 示例:
int num = 5;
int result = num++; // 此时 result 的值为 5,然后 num 变为 6
因此,区别在于前置形式先进行自增或自减操作,然后再使用变量;而后置形式则先使用变量的值,再进行自增或自减操作。
需要注意的是,无论是前置形式还是后置形式,都会使变量的值发生改变,只是在改变发生的时机上有所不同。
2.运算符的优先级
C语言中的运算符优先级如下(从高到低):(单算移关与,异或逻条赋)
- 括号:()
- 一元运算符:+(正)、-(负)、++、--、!、~、&、*
- 乘除取模:*、/、%
- 加减:+、-
- 移位:<<、>>
- 关系:>、>=、<、<=
- 相等性:==、!=
- 按位与:&
- 按位异或:^
- 按位或:|
- 逻辑与:&&
- 逻辑或:||
- 条件表达式:?:
- 赋值:=、+=、-=、*=、/=、%=、&=、^=、|=
- 逗号:,
七.C语言常用的输入输出函数
1.putchar和getchar
putchar
和 getchar
是在 C 语言中用于进行字符输入输出的函数。
-
putchar
函数用于输出一个单个字符到标准输出,即屏幕上。它的函数原型为int putchar(int character)
,其中character
是要输出的字符的 ASCII 值。putchar
函数会将字符输出到标准输出缓冲区,并返回输出的字符。例如,putchar('A')
会输出字符'A'
到屏幕上。 -
getchar
函数用于从标准输入中获取一个字符。它的函数原型为int getchar(void)
,它从标准输入缓冲区中读取一个字符并返回其 ASCII 值。例如,char ch = getchar()
会从键盘输入中读取一个字符并将其赋值给变量ch
。
这两个函数一般被广泛应用于简单的字符输入输出操作,可用于实现基本的文本输入输出功能。
2.puts和gets
puts
和 gets
是 C 语言中用于输入输出的函数。
-
puts
函数用于输出字符串到标准输出(通常为屏幕)。它的声明如下:int puts(const char *str);
puts
函数会输出字符串str
,并在末尾添加一个换行符。它会自动在输出的字符串后面添加一个换行符\n
。puts("Hello, World!");
gets
函数用于从标准输入(通常为键盘)读取一行字符串。但由于gets
存在安全问题,容易导致缓冲区溢出,因此不推荐在实际应用中使用。建议使用更安全的fgets
函数来代替。 -
总结:
puts
用于输出字符串,并自动添加换行符。gets
用于从标准输入读取一行字符串,但不安全,推荐使用fgets
函数。#include <stdio.h> int main() { puts("全力以赴xiaoyu");//自带\n可以传输字符串 puts("hello\0world");//遇到\0终止 char str[] = "www.baidu.com"; puts(str);//可以传输保存字符串的数组的首地址 char *p = "xiaoyu"; puts(p);//可以保存字符串的首地址的指针 char str2[32] = {0}; gets(str2); puts(str2); return 0; }
注意这里
#include <stdio.h> int main() { char str[] = "www.baidu.com"; // str 的初始内容 puts(str); // 输出原内容:www.baidu.com gets(str); // 从键盘输入新内容(覆盖原内容) puts(str); // 输出新内容 return 0; }
3.printf和scanf
1.printf
和 scanf
是 C 语言中常用的格式化输入输出函数。
1.1printf
函数用于格式化输出到标准输出(通常为屏幕)。它的声明如下:
int printf(const char *format, ...);
printf
函数根据格式字符串 format
中的格式化标识符将数据输出到标准输出流。常见的格式化标识符包括 %d
(整数)、%f
(浮点数)、%s
(字符串)等。
1.2scanf
函数用于从标准输入(通常为键盘)读取输入,并根据格式化字符串解析输入数据并将其存储到指定的变量中。它的声明如下:
int num = 10;
printf("The number is %d\n", num);
2.可以在占位符中加入格式修饰符,控制输出的宽度、精度和对齐方式:
%lf是C语言中用于格式化输入输出的占位符,用于表示输出或输入一个双精度浮点数(double类型)。
2.1. 宽度与对齐
-
%5d
:输出至少占5字符宽度,不足用空格填充(默认右对齐)。 -
%-5d
:左对齐。 -
%05d
:用0
填充空白(如005
)。
2.2. 精度控制
-
%.2f
:保留2位小数。 -
%5.2f
:总宽度5字符,保留2位小数。
2.3. 其他修饰符
-
%+d
:强制显示正负号(如+10
)。 -
%#x
:显示十六进制前缀0x
(如0xff
)。
2.4.scanf
的占位符与 printf
类似,但需注意:
-
输入字符串时,
%s
会忽略空格,遇到空格结束输入。 -
读取字符串到数组时,需指定最大宽度防止溢出:
char str[10]; scanf("%9s", str); // 最多读取9字符(留1位给结尾的 '\0')
具体请看数组部分,读取的时候虽然输入123 1其实读取的是12 3 后面1没管,
格式字符串中的空格表示跳过任意数量的空白字符(包括零个),此时输入缓冲区的下一个字符是 3
(非空白字符),因此空格匹配失败,scanf
直接进入下一步。%d
尝试从当前输入位置读取整数,当前输入位置是 3
。3
被读取并赋值给 a
→ a = 3
。剩余输入缓冲区内容: 1
(未被处理)。
2.5.解决:输入的时候用逗号隔开就没影响了
在C语言的scanf
函数中,格式字符串"%d,%f"
中的逗号(,
)表示精确匹配输入中的逗号,逗号前后不能有空格。以下是详细分析:
2.5.1. 格式字符串的规则
-
普通字符(如逗号):必须与输入流中的字符严格匹配。
-
空格字符:在格式字符串中,空格表示跳过输入中的任意数量空白字符(包括空格、制表符、换行符等)。
2.5.2. 你的代码场景
scanf("%d,%f", &a, &b);
输入要求:必须为 整数,浮点数
,逗号前后不能有空格。
例如:123,45.6
(正确),123 ,45.6
(错误,逗号前有空格)。
-
若输入带空格:
输入123 ,45.6
→%d
读取123
,但逗号前的空格导致匹配失败,scanf
停止解析,b
未被赋值。
2.5.3. 如何允许输入中的空格?
在格式字符串的逗号前后添加空格,使其更灵活:
scanf("%d , %f", &a, &b); // 允许逗号前后有任意空格
-
输入示例:
-
123,45.6
(正确) -
123 , 45.6
(正确) -
123 , 45.6
(正确)
-
2.6 scanf("%d\n",&a);为什会输入两次
在 C 语言中,当你使用 `scanf("%d\n", &a)` 时,输入两次的现象是由于格式字符串中的 `\n`(换行符)导致的。以下是详细解释:
`scanf` 的格式字符串中的 空白字符(如空格、制表符 `\t`、换行符 `\n`) 会匹配输入流中的 任意数量*的空白字符(包括 0 个或多个)。具体到你的代码:
scanf("%d\n", &a); // 格式字符串中的 \n 会导致问题
执行流程
第一步: `%d` 会读取一个整数(例如输入 `42` 后按回车)。
第二步:`\n` 会尝试匹配输入流中的“零个或多个空白字符”,但此时输入流中已经有回车键输入 的 `\n`。
第三步:由于 `\n` 在格式字符串中,`scanf` 会持续等待输入流中非空白字符的出现,直到用 户再次输入内容并按下回车。
示例分析
假设你输入 `42` 后按回车:
1. 输入缓冲区内容:`42\n`。
2. `scanf` 读取 `42`,但格式字符串中的 `\n` 会要求继续匹配后续的空白字符。
3. 此时程序会“卡住”,直到你输入任意非空白字符(例如再输入一个数字或随便按一个字母后回车):
- 输入 `5` 后回车:缓冲区变为 `5\n`。
- `scanf` 发现非空白字符 `5`,停止匹配 `\n`,但此时 `5` 会被留在输入缓冲区中,导致下一次输入被污染。
2.7.scanf()输入里出现空格就停止的解决办法
char str[32] = {0};
scanf("%[^\n]",str);
2.8.scanf("%d%d", &a, &b)
这个%d 会自动跳过空白,所以输入的时候,123 456就行不需要123456
八.转义字符
在C语言中,转义字符(Escape Characters) 是以反斜杠 \
开头的特殊字符序列,用于表示无法直接输入的字符(如换行、制表符等)或特殊含义的字符(如引号)。以下是详细的分类和示例:
1.基本转义字符
1. 换行符 \n
用于在输出中换行。
printf("Hello\nWorld");
// 输出:
// Hello
// World
2. 制表符 \t
生成水平制表符,常用于对齐文本。
printf("Name:\tAlice\nAge:\t25");
// 输出:
// Name: Alice
// Age: 25
3. 反斜杠 \\
和引号 \"
、\'
输出反斜杠或引号(避免与语法冲突)。
printf("路径:C:\\Program Files\\\n");
printf("他说:\"你好!\"");
// 输出:
// 路径:C:\Program Files\
// 他说:"你好!"
4. 空字符 \0
表示字符串的结束标志。
char str[] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 等价于 "Hello"
5. 其他控制字符
\a
:触发系统响铃(部分终端可能不支持)。
\b
:退格(删除前一个字符)。
2.八进制和十六进制转义
C语言允许通过ASCII码直接表示字符:
八进制转义:\ddd
(最多3位八进制数,范围 \0
~ \377
)。
printf("\101"); // 输出 'A'(ASCII码 65 = 八进制 101)
十六进制转义:\xhh
(1~2位十六进制数,范围 \x00
~ \xFF
)
printf("\x41"); // 输出 'A'(ASCII码 65 = 十六进制 0x41)
3.常见错误与注意事项
忘记转义反斜杠:
printf("C:\Program Files"); // 错误!输出 "C:Program Files"
printf("C:\\Program Files"); // 正确
误用转义字符:
printf("换行符是:\n"); // 实际会换行,而非显示 "\n"
printf("换行符是:\\n"); // 正确显示 "\n"
字符串结束符 \0
:
手动定义字符数组时需显式添加 \0
,否则可能导致越界。
char str[5] = {'H', 'e', 'l', 'l', 'o'}; // 错误!缺少 \0
char str[6] = {'H', 'e', 'l', 'l', 'o', '\0'}; // 正确
还有十六进制和八进制
#include <stdio.h>
#include <string.h>
int main()
{
putchar('1');
putchar('\n');
putchar('2');
putchar('\12');//八进制
putchar('3');
putchar('\112');//J
putchar('\x0a');//十六进制
putchar('4');
return 0;
}
九.分支控制语句
9.1 if else
9.11 if else基本语法
单条件判断(if
)
if (条件表达式) {
// 条件为真时执行的代码
}
int age = 18;
if (age >= 18) {
printf("已成年\n");
}
二选一(if-else
)
if (条件表达式) {
// 条件为真时执行
} else {
// 条件为假时执行
}
int score = 85;
if (score >= 60) {
printf("及格\n");
} else {
printf("不及格\n");
}
多条件判断(else if
)
if (条件1) {
// 条件1为真时执行
} else if (条件2) {
// 条件2为真时执行
} else {
// 其他情况执行
}
int temperature = 25;
if (temperature > 30) {
printf("炎热\n");
} else if (temperature > 20) {
printf("舒适\n");
} else {
printf("凉爽\n");
}
9.12. C语言特有的注意事项
1.2.1条件表达式:
必须是 整型表达式(C语言用 0
表示假,非 0
表示真)。
注意区分 =
(赋值)和 ==
(比较)的误用:
if (x = 5) { ... } // 错误!将 x 赋值为 5,条件永远为真
if (x == 5) { ... } // 正确
1.2.2代码块与作用域:
使用 {}
明确代码块范围,避免悬空 else
问题。
错误示例(悬空 else
):
if (a > 0)
if (b > 0)
printf("a和b均为正数\n");
else
printf("a <= 0\n"); // 实际属于内层 if,而非外层!
正确写法:
if (a > 0) {
if (b > 0) {
printf("a和b均为正数\n");
}
} else {
printf("a <= 0\n"); // 明确作用域
}
简化代码:
if (x > 0)
printf("正数\n");
else
printf("非正数\n");
9.13. 嵌套 if-else
int num = 10;
if (num > 0) {
printf("正数\n");
if (num % 2 == 0) {
printf("且是偶数\n");
} else {
printf("且是奇数\n");
}
} else {
printf("非正数\n");
}
三元运算符(简化 if-else
),可以看上一个文章
C语言支持三元运算符 ? :
,用于简化简单条件判断:
int x = 5;
char* result = (x > 0) ? "正数" : "非正数";
printf("%s\n", result); // 输出:正数
9.14. 常见错误与调试
比较浮点数:避免直接比较浮点数(精度问题):
float a = 0.1 + 0.2;
if (a == 0.3) { ... } // 可能为假!
if (fabs(a - 0.3) < 1e-6) { ... } // 正确方式
逻辑运算符:使用 &&
(逻辑与)、||
(逻辑或)、!
(逻辑非):
if (x > 0 && x < 100) { ... } // x在(0, 100)区间
短路求值:&&
和 ||
会短路后续条件判断:
if (ptr != NULL && *ptr == 10) { ... } // 安全访问指针
来个完整例子
#include <stdio.h>
int main()
{
int Long = 0;
int Wide = 0;
int High = 0;
scanf("%d%d%d", &Long, &Wide, &High);
if(Long>0&&Wide>0&&High>0){
if(Long==Wide&&Wide==High){
printf("这是一个正方形");
}else{
printf("这是一个长方形");
}
}else if(Long==0|Wide==0|High==0){
printf("你在瞎扯");
}else{
printf("更扯了");
}
return 0;
}
例子2
#include<stdio.h>
int main()
{
int a = 0;
int b = 0;
int c = 0;
scanf("%d%d%d", &a, &b, &c);
if(a>0&&b>0&&c>0){
if(a<(b+c)&&b<(a+c)&&c<(a+b)){
if(a==b|a==c|b==c){
if(a==b&&b==c){
printf("等边三角形");
}else{
printf("等腰三角形");
}
}else if(a^2+b^2==c^2||a^2+c^2==b^2||b^2+c^2==a^2||){
printf("直角三角形");
}else{
printf("普通三角形");
}
}else{
printf("无法构成三角形");
}
}else{
printf("请输入正常数字");
}
}
常见误区
-
误区:认为
a == b == c
等效于a == b && b == c
。 -
事实:
a == b == c
的实际含义是(a == b) == c
,与三个变量是否相等无关。 -
危险点:当
c
的值为0
或1
时,代码可能“碰巧”正确,但这是不可靠的。
9.2 switch case
C语言中的switch
语句是一种多分支选择结构,用于根据表达式的值选择不同的代码块执行。它常用于替代多个if-else
语句,使代码更简洁清晰。
9.21.基本语法
switch (表达式) {
case 常量1:
// 代码块1
break;
case 常量2:
// 代码块2
break;
// 更多case...
default:
// 默认代码块(可选)
}
关键点
-
表达式
-
必须是整型或枚举类型(如
int
、char
)。 -
不支持浮点数、字符串等类型。
-
-
case标签
-
case
后的值必须是常量(如1
、'A'
),不能是变量或表达式。 -
每个
case
的值必须唯一,不可重复。
-
-
break语句
-
执行完一个
case
后,若未遇到break
,会继续执行后续的case
(称为“穿透”)。 -
合理利用穿透可合并多个
case
的逻辑(需注释说明)。
-
-
default分支
-
可选的默认分支,处理未匹配任何
case
的情况。 -
通常放在末尾,但位置不影响逻辑。
-
#include <stdio.h>
int main() {
int day = 3;
switch (day) {
case 1:
printf("星期一\n");
break;
case 2:
printf("星期二\n");
break;
case 3:
printf("星期三\n");
break;
case 4:
printf("星期四\n");
break;
default:
printf("无效输入\n");
}
return 0;
}
9.22.注意事项
-
case穿透
-
若未写
break
,程序会继续执行下一个case
的代码,直到遇到break
或switch
结束。 -
示例(合并多个
case
):switch (month) { case 1: case 3: case 5: // 1、3、5月共用逻辑 printf("31天\n"); break; case 2: printf("28或29天\n"); break; }
-
default分支
-
即使所有情况已被覆盖,仍建议保留
default
以增强代码健壮性。
-
-
作用域限制
-
在
case
代码块中定义变量时,需用大括号{}
限定作用域,避免编译错误。
-
-
#include <stdio.h>
int main()
{
int day = 0;
printf("请输入数字:");
scanf("%d",&day);
switch(day){
case 1:printf("星期一\n");break;
case 2:printf("星期二\n");break;
case 3:printf("星期三\n");break;
case 4:printf("星期四\n");break;
case 5:printf("星期五\n");break;
case 6:printf("星期六\n");break;
case 7:printf("星期七\n");break;
default :printf("别瞎扯\n");break;
}
return 0;
}
例子2
#include <stdio.h>
int main()
{
int lvalue = 0;
int rvalue = 0;
char operator = 0;
scanf("%d%c%d", &lvalue, &operator, &rvalue);
switch(operator){
case '+':
printf("%d + %d = %d",lvalue,rvalue,lvalue+rvalue);
break;
case '-':
printf("%d - %d = %d",lvalue,rvalue,lvalue-rvalue);
break;
case '*':
printf("%d * %d = %d",lvalue,rvalue,lvalue*rvalue);
break;
case '/':
printf("%d / %d = %d",lvalue,rvalue,lvalue/rvalue);
break;
case '%':
printf("%d %% %d = %d",lvalue,rvalue,lvalue%rvalue);
break;
default:printf("不要瞎扯");
}
return 0;
}
十.循环控制语句
10.1 goto
在C语言中,goto
语句是一种无条件跳转控制语句,允许程序直接跳转到代码中定义的标签位置。虽然goto
在某些特定场景下有用,但它也因容易导致代码结构混乱而备受争议。
10.11.基本语法
goto label; // 跳转到标签处
...
label: // 标签定义(以冒号结尾)
// 代码块
核心特性
-
标签定义
-
标签是用户自定义的标识符,后跟冒号(
:
),例如error_handling:
。 -
标签必须在同一函数内定义,不可跨函数跳转。
-
-
跳转范围
-
goto
只能跳转到当前函数内的标签,不能跳出或跳入其他函数。
-
-
执行流程
-
跳转完全忽略代码顺序,直接转移到目标标签处继续执行。
-
10.12.典型应用场景
1. 错误处理集中化
在资源分配(如内存、文件句柄)失败时,用goto
跳转到统一清理代码,避免重复逻辑:
int func() {
FILE *fp = fopen("file.txt", "r");
if (!fp) goto error;
int *data = malloc(100 * sizeof(int));
if (!data) goto cleanup_file;
// 正常逻辑...
free(data);
fclose(fp);
return 0;
cleanup_file:
fclose(fp);
error:
printf("发生错误\n");
return -1;
}
2. 退出多层嵌套
在多重循环或条件嵌套中,快速跳出深层结构:
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
if (condition) {
goto exit_loops; // 直接跳出所有循环
}
}
}
exit_loops:
printf("提前退出循环\n");
10.13.争议与缺点
-
破坏结构化编程
-
随意使用
goto
会导致代码逻辑混乱(“面条代码”),降低可读性和可维护性。
-
-
作用域问题
-
跳转可能绕过变量初始化或资源释放,引发未定义行为:
goto skip; // 跳过初始化 int x = 10; // 未执行 skip: printf("%d", x); // 错误:x未初始化
-
-
替代方案更优
-
多数情况下可用以下结构化方法替代:
-
函数封装(将代码块提取为函数)
-
循环控制(
break
、continue
) -
错误处理标志(通过返回值传递状态)
-
-
使用原则
-
限制使用范围
-
仅在错误处理或退出多层嵌套时谨慎使用。
-
-
向前跳转
-
尽量让
goto
向前跳转(而非向后),避免形成循环。
-
-
注释说明
-
对
goto
的作用和跳转目标添加清晰注释。
-
不推荐用法(逻辑混乱)
int x = 0;
start:
x++;
if (x < 5) goto start; // 类似循环,但难以阅读
推荐替代(结构化循环)
for (int x = 0; x < 5; x++) {
// 清晰且易维护
}
总结
-
能用但慎用:
goto
在C语言中未被废弃,但应严格限制使用场景。 -
替代优先:优先使用函数、循环和条件语句实现结构化逻辑。
-
规范命名:标签名应明确表达意图(如
cleanup:
、error:
)。
10.2 while循环
while
循环 是一种基础且灵活的条件控制结构,用于在满足特定条件时重复执行代码块。
10.21.基本语法
while (条件表达式) {
// 循环体(条件为真时执行)
}
执行流程
-
判断条件:先检查条件表达式是否为真(非零)。
-
执行循环体:若条件为真,执行循环体内的代码。
-
重复判断:再次检查条件,重复上述步骤,直到条件为假时退出循环。
#include <stdio.h>
int main() {
int i = 1; // 初始化循环变量
while (i <= 5) { // 条件判断
printf("%d ", i);//打印数字1到5
i++; // 更新循环变量
}
return 0;
}
关键特性
-
先判断后执行
-
若初始条件为假,循环体一次也不执行。
-
与
do-while
循环(先执行后判断)形成对比。
-
-
灵活控制循环
-
条件表达式可以是任何返回整型值的表达式(如逻辑判断、函数调用等)。
-
循环体内需手动更新循环变量,否则可能导致死循环。
-
-
与
break
和continue
配合-
break
:立即退出整个循环。 -
continue
:跳过当前循环剩余代码,直接进入下一次条件判断。
-
10.22常见应用场景
1. 不确定次数的循环
// 读取用户输入直到输入为0
int num;
while (1) { // 无限循环
scanf("%d", &num);
if (num == 0) break; // 输入0时退出
printf("输入值:%d\n", num);
}
2. 遍历数据(如链表、数组)不懂得可以先跳过指针相关的,学习后再来看
struct Node *p = head; // 链表头指针
while (p != NULL) { // 遍历链表
printf("%d ", p->data);
p = p->next;
}
3. 输入验证
int value;
printf("请输入1-100之间的数:");
while (scanf("%d", &value) != 1 || value < 1 || value > 100) {
while (getchar() != '\n'); // 清空输入缓冲区
printf("输入无效,请重新输入:");
}
10.23.注意事项
避免死循环
-
确保循环体内有条件变量更新,例如:
int i = 0; while (i < 10) { printf("%d", i); // 缺少 i++ 会导致死循环! }
作用域限制
-
循环体内定义的变量仅在循环内有效(C99及以上支持):
while (int i = 0) { ... } // 错误!C语言不支持循环条件中定义变量
-
慎用无限循环
-
使用
while(1)
时需确保有明确的退出条件(如break
)。
-
计算1加到100
猴子吃桃问题,每天吃一半多一个,直到第十天还没吃发现只有一个桃了,问原来多少桃
10.3 do ...while
do...while
循环 是一种后测试循环结构,特点是先执行循环体,再检查条件。与 while
循环不同,do...while
至少执行一次循环体,适用于需要先执行操作再判断是否继续的场景。
10.31.基本语法
do {
// 循环体(至少执行一次)
} while (条件表达式); // 注意结尾的分号!
执行流程
-
执行循环体:无条件执行一次循环体内的代码。
-
检查条件:判断条件表达式是否为真(非零)。
-
重复或退出:若条件为真,重复执行循环体;否则退出循环。
输入验证(确保用户输入有效值)
#include <stdio.h>
int main() {
int num;
do {
printf("请输入1-10之间的整数:");
scanf("%d", &num);
} while (num < 1 || num > 10); // 输入无效时重复提示
printf("有效输入:%d\n", num);
return 0;
}
核心特性
-
至少执行一次
-
即使初始条件为假,循环体也会先执行一次。
-
对比
while
:若初始条件为假,循环体完全不执行。
-
-
条件灵活性
-
条件表达式可以是任意返回整型值的逻辑或函数调用。
-
-
与
break
和continue
配合-
break
:立即终止循环。 -
continue
:跳过剩余代码,直接进入下一次条件判断。
-
10.32.典型应用场景
1.菜单驱动程序
用户需要先看到菜单选项,再根据输入决定是否退出:
char choice;
do {
printf("1. 开始游戏\n");
printf("2. 加载存档\n");
printf("3. 退出\n");
printf("请输入选项:");
scanf(" %c", &choice);
// 处理选项逻辑...
} while (choice != '3'); // 输入3时退出
2.数据批量处理
先读取数据,再根据数据内容决定是否继续:
FILE *fp = fopen("data.txt", "r");
if (!fp) exit(1);
char buffer[100];
do {
fgets(buffer, sizeof(buffer), fp); // 先读取一行
if (feof(fp)) break; // 文件结束时退出
printf("%s", buffer);
} while (1); // 无限循环,依赖内部break退出
fclose(fp);
3.游戏/模拟循环
至少执行一次游戏逻辑,再根据状态判断是否继续:
int player_alive = 1;
do {
update_game(); // 更新游戏状态
render_graphics(); // 渲染画面
player_alive = check_player_status(); // 检查玩家是否存活
} while (player_alive); // 玩家存活时继续循环
10.34注意事项
-
避免死循环
int i = 0; do { printf("%d ", i); i++; } while (i < 5); // 正常退出
-
分号不可省略
-
while(条件表达式)
后的分号是语法必需,否则编译错误。
-
-
变量作用域
-
循环体内定义的变量仅在当前迭代有效(C99及以上支持):
do { int temp = 10; // 每次循环重新定义 printf("%d ", temp); } while (0); // 仅执行一次
10.4 for 循环
for
循环 是一种高度结构化的循环控制语句,适用于已知循环次数或需要集中管理循环变量的场景。它通过将初始化、条件检查和变量更新整合在一行代码中,使循环逻辑更加清晰紧凑。
10.41.基本语法
for (初始化表达式; 条件表达式; 更新表达式) {
// 循环体
}
执行流程
-
初始化:执行一次初始化表达式(通常定义循环变量)。
-
条件检查:判断条件表达式是否为真(非零)。
-
执行循环体:条件为真时执行循环体代码。
-
更新变量:执行更新表达式(通常修改循环变量)。
-
重复步骤2-4,直到条件为假时退出循环。
打印数字0到4
#include <stdio.h>
int main() {
for (int i = 0; i < 5; i++) { // 初始化i=0,条件i<5,每次i自增1
printf("%d ", i);
}
return 0;
}
10.42.核心特性
-
三表达式集中管理
-
初始化表达式:定义并初始化循环变量(可定义多个变量,用逗号分隔)。
-
条件表达式:控制循环是否继续执行。
-
更新表达式:每次循环后执行的变量修改操作。
-
-
灵活变体
int i = 0; for (; i < 5; ) { // 省略初始化和更新表达式 printf("%d ", i); i++; // 在循环体内更新变量 }
-
多变量控制,使用逗号运算符同时操作多个变量:
for (int i = 0, j = 10; i < j; i++, j--) { printf("i=%d, j=%d\n", i, j); }
10.43.典型应用场景
1. 遍历数组
int arr[] = {1, 2, 3, 4, 5};
for (int i = 0; i < sizeof(arr)/sizeof(arr[0]); i++) {
printf("%d ", arr[i]);
}
2. 嵌套循环(如矩阵运算)
for (int i = 0; i < 3; i++) { // 行循环
for (int j = 0; j < 3; j++) { // 列循环
printf("(%d,%d) ", i, j);
}
printf("\n");
}
3. 延迟循环(裸机编程)
// 简单延时实现(实际需用硬件定时器)
for (volatile int i = 0; i < 1000000; i++);
10.44.注意事项
-
避免死循环
for (int i = 0; ; i++) { // 条件表达式缺失,无限循环! if (i >= 5) break; // 必须手动退出 }
-
变量作用域
for (int i = 0; i < 5; i++) { ... } // 此处i不可访问
-
慎改循环变
for (int i = 0; i < 5; i++) { if (i == 2) i = 4; // 跳过i=3,直接到i=4 printf("%d ", i); // 输出:0 1 4 }
10.45.高级用法
无限循环(替代 while(1)
)
for (;;) { // 经典写法
// 需内部用break退出
}
倒序循环
for (int i = 9; i >= 0; i--) {
printf("%d ", i); // 输出:9 8 7 ... 0
}
位运算控制
for (unsigned int mask = 0x01; mask != 0; mask <<= 1) {
// 遍历二进制位(如0x01→0x02→0x04...)
}
找出100到1000的水仙花数
注:方便计算的两个函数
pow和sqrt
头文件都是#include<math.h>
函数原型:double pow(double x,double y);计算x的y次幂
double sqrt(double x);计算x的非负平方根
注意要链接库如图 编译的时候是gcc 7.pow和sqrt.c -lm
10.5 死循环
在 C 语言中,死循环是指在程序执行过程中无法退出的循环结构。死循环通常用于需要持续执行某些任务的情况,例如服务器程序、嵌入式系统等。下面是一个简单的示例:
#include <stdio.h>
int main() {
while(1) {
printf("这是一个死循环\n");
}
return 0;
}
在这个示例中,while(1)
创建了一个永远为真的条件,因此循环体中的代码将无限地执行下去,导致程序陷入死循环状态。要退出死循环,通常可以通过手动中断程序的执行,比如使用 Ctrl+C 来终止程序。
循环的用法例子:
#include <stdio.h>
int main()
{
char Char1 = 95;//下划线
char Char2 = 70;//字母f
int i = 0;//行数
int j = 0;//每行出现次数
for(i=0;i<6;i++){//一共6行
j = i;//根据行确定次数
while(j--){
putchar(Char1);
}
j = i+1;
while(j--){
putchar(Char2--);//完成变换
}
putchar('\n');//一行完成
Char2 = 70;
}
return 0;
}
十一.辅助控制关键字
11.1 break
11.11.break
的用途
1.终止循环
在 for
、while
或 do...while
循环中,break
会立即跳出当前循环,继续执行循环外的代码。
示例:找到第一个满足条件的数字后立即退出循环:
int target = 5;
for (int i = 0; i < 10; i++) {
if (i == target) {
printf("Found %d!\n", target);
break; // 找到后直接退出循环
}
}
2.终止 switch
语句
在 switch
语句中,break
用于**防止“穿透”(fall-through)**到下一个 case
。
示例:
char grade = 'B';
switch (grade) {
case 'A':
printf("Excellent\n");
break; // 必须加 break,否则会执行下一个 case
case 'B':
printf("Good\n");
break;
default:
printf("Invalid\n");
}
11.12.关键特性
1.仅作用于最内层结构
break
只会跳出直接包含它的循环或 switch
。如果是嵌套循环,无法直接跳出外层循环。
示例:
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
if (j == 1) {
break; // 仅跳出内层循环,外层循环继续执行
}
printf("i=%d, j=%d\n", i, j);
}
}
2.不能用于 if
语句
break
只能用于循环或 switch
,在 if
语句中直接使用会导致编译错误。
break` 不能直接用于独立的 `if` 语句,但可以用于嵌套在循环或 `switch` 内部的 `if` 语句,因为此时 `break` 的实际作用是跳出外层的循环或 `switch`,而不是跳出 `if`。
1. 代码结构
- `break` 虽然在 `if (j == 1)` 的代码块内,但它**实际属于外层的内层 `for (j = 0; ...)` 循环
- `break` 的作用是**跳出内层 `for` 循环**,而不是跳出 `if` 语句。
2. 执行逻辑
- 当 `j == 1` 时,触发 `break`,此时内层 `for` 循环立即终止,继续执行外层 `for` 循环的下一次迭代(`i++`)。
- 因此,对于每个外层循环的 `i`,内层循环只会执行 `j = 0` 的情况(`j = 1` 时被 `break` 终止)。
3. 输出结果
i=0, j=0
i=1, j=0
i=2, j=0
为什么说“`break` 不能用于 `if`”
- 错误示例:如果 `break` 不在任何循环或 `switch` 中,直接用于 `if`,会导致编译错误:
if (x > 5) {
break; // 错误!此处没有循环或 switch 供 break 跳出
}
11.2 continue
在 C 语言中,continue
是一个流程控制关键字,用于跳过当前循环迭代的剩余代码,直接进入下一次循环的条件判断。它的核心作用是让循环“提前结束当前迭代”,但不会终止整个循环。
11.21.continue
的核心行为
-
立即中断当前迭代
当continue
被执行时,循环体内位于continue
之后的代码会被跳过,直接进入循环的条件判断或步进表达式(如for
循环的i++
)。 -
仅影响当前循环
-
在嵌套循环中,
continue
仅作用于最内层的循环。 -
与
break
不同,continue
不会终止循环,而是让循环继续执行下一次迭代
-
11.22.使用场景示例
1. 跳过不符合条件的数据
for (int i = 0; i < 10; i++) {
if (i % 2 == 0) {
continue; // 跳过偶数
}
printf("%d ", i); // 输出 1 3 5 7 9
}
2. 避免执行无效操作
while (读取数据()) {
if (数据无效) {
continue; // 跳过无效数据的处理
}
处理数据(); // 仅处理有效数据
}
3.与 break
的对比
行为 | continue | break |
---|---|---|
作用范围 | 仅跳过当前迭代,继续下一次循环 | 直接终止整个循环 |
循环是否继续 | 是 | 否 |
典型场景 | 过滤数据、跳过特定条件 | 提前找到目标、错误处理 |
11.23.关键注意事项
-
continue
不能用于switch
语句switch (x) { case 1: continue; // 错误!continue 只能用于循环 // ... }
-
在
for
循环中的特殊行为
for
循环执行continue
后,仍会执行步进表达式(如i++
),再进入条件判断:for (int i = 0; i < 3; i++) { if (i == 1) continue; printf("%d ", i); // 输出 0 2(i=1 时跳过打印,但仍会执行 i++) }
-
避免死循环
在while
或do...while
中使用continue
时,需确保循环条件能被修改,否则可能导致死循环:int i = 0; while (i < 5) { if (i == 2) { continue; // 死循环!i 无法自增到 3 } printf("%d ", i); i++; }
找出输入的所有因子
找出【2,999】中的完美数(所有因数加一起等于该数字)
判断【1.100】的质数
11.3 return
在 C 语言中,`return` 是一个核心关键字,用于从函数中返回一个值并终止当前函数的执行。它直接影响程序的流程控制和函数间的数据传递。
11.31.`return` 的基本作用
1. 返回值给调用者
在非 `void` 函数中,`return` 将指定值传递给函数的调用方。
int add(int a, int b) {
return a + b; // 返回两数之和
}
2. 终止函数执行
无论函数是否返回值,`return` 都会立即结束当前函数的执行,后续代码不再运行。
void check(int x) {
if (x < 0) {
printf("Invalid input!\n");
return; // 提前结束函数,不执行后续代码
}
printf("Processing...\n");
}
11.32.语法规则
1. 非 `void` 函数
- 必须通过 `return` 返回一个与函数声明类型匹配的值。
- 若未返回,可能导致未定义行为(编译器可能警告)。
int get_value() {
// 缺少 return 语句
}
2. `void` 函数
- 可以不写 `return`,或使用 `return;` 提前退出。
- 不可返回任何值。
void log_message(char* msg) {
if (msg == NULL) return; // 合法
printf("Log: %s\n", msg);
}
11.33.`return` 的进阶用法
1. 多条件提前返回
通过条件分支提前退出函数,减少嵌套层级:
int divide(int a, int b) {
if (b == 0) return -1; // 错误处理提前返回
return a / b; // 正常逻辑
}
2. 返回指针
需确保返回的指针指向的内存有效(如静态变量或堆内存):
int* create_array(int size) {
int* arr = (int*)malloc(size * sizeof(int));
return arr; // 返回堆内存指针
}
3. 递归中的 `return`
递归函数通过 `return` 传递结果到上一层调用:
int factorial(int n) {
if (n <= 1) return 1;
return n * factorial(n - 1); // 递归返回
}
11.34.注意事项
1. `main` 函数的返回值
- `int main()` 通常返回 `0` 表示成功,非零表示错误(遵循操作系统约定)。
- C99 后允许 `main` 隐式返回 `0`,但显式写出更规范。
2. 避免返回局部变量指针
int* dangerous() {
int x = 10;
return &x; // 错误!x 在函数结束后被销毁
}
3. 函数结尾必须覆盖所有路径
若函数声明有返回值,需确保所有条件分支都有 `return`,否则可能引发未定义行为。
十二.数组
在C语言中,数组是一种用于存储相同类型数据元素的连续内存结构。它是编程中最基础且重要的数据结构之一,广泛用于批量处理数据、字符串操作、算法实现等场景。
12.1 数组的定义与特点
定义
数组是一组固定长度、相同类型的元素集合,每个元素通过下标(索引)访问。
语法:
数据类型 数组名[数组长度]; // 一维数组
数据类型 数组名[行数][列数]; // 二维数组(多维类推)
核心特性
- 连续内存:所有元素在内存中顺序存储,地址连续。
- 固定大小:数组长度在声明时确定,运行时不可修改(C99前)。
- 下标访问:通过索引从`0`到`n-1`访问元素(下标越界是未定义行为)。
12.11. 数组的声明与初始化
(1) 声明数组
int numbers[5]; // 声明一个长度为5的整型数组(元素值未初始化)
float matrix[3][3]; // 声明3x3的浮点型二维数组
(2) 初始化数组
- 完全初始化:
int arr1[3] = {10, 20, 30}; // 显式初始化所有元素
int arr2[] = {1, 2, 3}; // 自动推导长度为3
- 部分初始化:未赋值的元素默认初始化为`0`。
int arr3[5] = {1, 2}; // 等价于 {1, 2, 0, 0, 0}
- 字符数组(字符串):
char str1[] = "Hello"; // 自动包含结尾的'\0',长度6
char str2[5] = {'H', 'e'}; // 剩余元素为0,但可能不形成有效字符串
4.12.访问数组元素
通过下标运算符`[]`访问元素,下标从`0`开始。
int arr[5] = {10, 20, 30, 40, 50};
printf("%d\n", arr[0]); // 输出第一个元素:10
arr[2] = 100; // 修改第三个元素为100
/遍历数组的典型方式
int arr[5] = {10, 20, 30, 40, 50};
printf("%d\n", arr[0]); // 输出第一个元素:10
arr[2] = 100; // 修改第三个元素为100
12.2 数组的内存布局
数组元素在内存中按顺序连续存储。
- 一维数组:
int a[3] = {1, 2, 3};
内存布局:
地址低 → 高
[1][2][3]
- 二维数组(按行优先存储):
int b[2][3] = {{1, 2, 3}, {4, 5, 6}};
内存布局:
[1][2][3][4][5][6]
12.21.数组与指针的关系
数组名是数组首元素的地址(隐式指针),但二者不等价:
int arr[5] = {0};
int *p = arr; // p指向arr[0]的地址
// 以下等价:
arr[2] = 10;
*(arr + 2) = 10; // 通过指针算术访问元素
关键区别
- `sizeof(arr)`返回整个数组的字节大小。
- `sizeof(p)`返回指针变量的字节大小(如4或8字节)。
- 数组名不可重新赋值(如 `arr = p` 是非法的)。
多维数组
多维数组本质上是“数组的数组”。
二维数组示例
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
// 访问元素 matrix[行][列]
printf("%d", matrix[1][2]); // 输出6
内存视角
二维数组在内存中仍按一维连续存储,可通过指针遍历:
for (int i = 0; i < 2*3; i++) {
printf("%d ", *(&matrix[0][0] + i)); // 输出1 2 3 4 5 6
}
12.3 常见问题与注意事项
1. 下标越界
int arr[3] = {0};
arr[3] = 10; // 越界访问!可能导致程序崩溃或数据污染
2. 数组长度必须为常量(C99前)
int size = 5;
int arr[size]; // C99前非法,C99后支持变长数组(VLA)
3. 数组作为函数参数
数组会退化为指针,需额外传递长度信息:
void printArray(int arr[], int size) { // 等价于 int *arr
for (int i = 0; i < size; i++) {
printf("%d ", arr[i]);
}
}
4.在前面有注意到一个代码里有一个
int str【3】={0};
scanf("%2s",str);
在C语言中,数组是否需要为特殊终止符(如 `\0`)预留空间,取决于数组的类型和用途。你提到的 `int arr1[3] = {10, 20, 30};` 是一个整型数组,而 `\0` 是字符数组(字符串)的终止符。它们的规则完全不同,以下是详细解释:
你的代码 `int arr1[3]` 为何不需要 `\0`?
- 整型数组的本质:
`int arr1[3] = {10, 20, 30};` 定义了一个长度为3的整型数组,每个元素都是独立的整数。
- 程序通过下标(如 `arr1[0]`)直接访问元素,无需依赖终止符断边界。
- 数组长度在定义时已固定(3),编译器知道共有3个元素。
- 内存视角:
假设 `int` 占4字节,数组总占用 `3*4=12` 字节:
```
[10][20][30] // 每个方框代表4字节
```
无任何额外空间需求,也无需 `\0`。
字符数组为何需要 `\0`?
字符数组用于表示字符串时,必须显式或隐式包含 `\0`,因为字符串处理函数(如 `printf`、`strlen`)依赖 `\0` 确定字符串的结束位置。
示例对
- 正确示例(预留 `\0` 空间):
char str[4] = "abc"; // 隐式添加 \0,实际内容为 {'a','b','c','\0'}
- 长度4的数组可存储3字符 + 1终止符。
- 错误示例(未预留 `\0`):
char str[3] = "abc"; // 无空间存放 \0,可能导致越界访问
printf("%s", str); // 未定义行为(可能输出乱码或崩溃)
如果强行给整型数组加 `\0` 会怎样?**
假设尝试以下代码:
int arr2[4] = {10, 20, 30, '\0'}; // \0 等价于整数值0
- 语法合法:`\0` 是ASCII字符,对应整数值0,等价于:
int arr2[4] = {10, 20, 30, 0}; // 完全合法
char s[4] = {‘s’‘s’‘s’};//其实后面还有一个0,相当于‘\0’
printf("%s",s);
- 逻辑无意义:整型数组不需要终止符,即使末尾有0,遍历时仍需通过下标控制范围。
#include <stdio.h>
int main() {
// 整型数组(无需\0)
int arr1[3] = {10, 20, 30};
for (int i = 0; i < 3; i++) {
printf("arr1[%d] = %d\n", i, arr1[i]); // 正常输出 10, 20, 30
}
// 字符数组(需要\0)
char str1[4] = "abc"; // 正确
char str2[3] = "abc"; // 错误(未预留\0)
printf("str1: %s\n", str1); // 输出 "abc"
printf("str2: %s\n", str2); // 未定义行为(可能输出乱码)
return 0;
}
5.字符数组初始化注意事项:
显式初始化
手动添加 \0
:若需将字符数组作为字符串使用,必须显式包含 \0
。
char str[4] = {'a', 'b', 'c', '\0'}; // 正确:可作为字符串
char str[3] = {'a', 'b', 'c'}; // 错误:无法作为字符串(无结束符)
字符串字面量初始化:
使用双引号初始化时,编译器会自动添加 \0
。
char str[] = "abc"; // 数组大小为4(包含 \0)
数组大小与字符串长度
预留 \0
的空间:数组长度必须比字符串的实际字符数至少多1。
char str[6] = "hello"; // 正确:5字符 + 1个 \0
char str[5] = "hello"; // 错误:数组越界(实际需要6字节)
6.scanf
的风险:scanf("%s", str)
不会检查输入长度,可能导致缓冲区溢出。
安全写法:限制读取长度(如 scanf("%9s", str)
读取最多9字符,预留 \0
)。
输出函数的使用printf
依赖 \0
:printf("%s", str)
会一直输出字符,直到遇到 \0
。若数组未以 \0
结尾,可能输出乱码或崩溃。
7.找出数组里的最大值和数组下标
该代码生成一个长度为20的数组,前两个元素由用户输入,后续元素为前两项之和(类似斐波那契数列),最后打印整个数组。
#include <stdio.h>
int main()
{
int arr[20] = {0};
int i = 0;
int num1 = 0;
int num2 = 0;
scanf("%d%d",&num1,&num2);
arr[0] = num1;
arr[1] = num2;
for(i=0;i<18;i++){
arr[i+2] = arr[i]+arr[i+1];
}
for(i=0;i<20;i++){
printf("%d ",arr[i]);
}
return 0;
}
12.4 二维数组
1. 二维数组的基本概念
二维数组可以看作是一个行和列的矩阵,例如:
int matrix[3][4]; // 声明一个3行4列的整型二维数组
2. 初始化方法
(1) 完全初始化
在声明时直接为所有元素赋值,显式指定每行的元素:
int matrix[2][3] = {
{1, 2, 3}, // 第一行
{4, 5, 6} // 第二行
};
(2) 部分初始化
-
省略行数:若未指定行数,编译器会根据初始化的数据自动推断行数。
int matrix[][3] = {
{1, 2, 3},
{4, 5, 6},
{7, 8, 9} // 行数为3
};
省略部分元素:未显式赋值的元素会被自动初始化为0
。
int matrix[3][3] = {
{1}, // 第一行:1, 0, 0
{4, 5}, // 第二行:4, 5, 0
{7, 8, 9} // 第三行:7, 8, 9
};
(3) 扁平化初始化
将所有元素按行顺序连续写出,编译器自动分配行列:
int matrix[2][3] = {1, 2, 3, 4, 5, 6};
// 等效于:
// { {1,2,3}, {4,5,6} }
(4) 动态初始化
通过嵌套循环为数组元素赋值(常用于运行时动态确定值):
int matrix[3][3];
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 3; j++) {
matrix[i][j] = i + j; // 例如:0,1,2; 1,2,3; 2,3,4
}
}
3. 示例代码
#include <stdio.h>
int main() {
// 完全初始化
int a[2][3] = {
{10, 20, 30},
{40, 50, 60}
};
// 部分初始化(省略行数)
int b[][4] = {
{1, 2}, // 第一行:1, 2, 0, 0
{3, 4, 5} // 第二行:3, 4, 5, 0
}; // 行数自动推断为2
// 输出数组内容
printf("Array a:\n");
for (int i = 0; i < 2; i++) {
for (int j = 0; j < 3; j++) {
printf("%d ", a[i][j]);
}
printf("\n");
}
return 0;
}
4. 注意事项
-
行数可省略,列数不可省略:
int arr[][3]
合法,但int arr[3][]
非法。 -
默认初始化为0:未显式赋值的元素会被初始化为
0
。 -
越界访问:需确保行列索引不超过数组声明的大小(如
arr[2][3]
的行索引范围是0~1
,列索引是0~2
)。
十三.冒泡方式
冒泡排序(Bubble Sort)是一种简单的排序算法,它重复地遍历待排序的元素,比较相邻元素的大小,并依次交换它们,直到整个序列按照从小到大(或从大到小)的顺序排列。
以下是使用C语言实现冒泡排序的基本思路:
#include <stdio.h>
void bubbleSort(int arr[], int n) {
for (int i = 0; i < n-1; i++) {
for (int j = 0; j < n-i-1; j++) {
if (arr[j] > arr[j+1]) {
// 交换arr[j]和arr[j+1]的位置
int temp = arr[j];
arr[j] = arr[j+1];
arr[j+1] = temp;
}
}
}
}
int main() {
int arr[] = {64, 34, 25, 12, 22, 11, 90};
int n = sizeof(arr) / sizeof(arr[0]);
bubbleSort(arr, n);
printf("排序后的数组:\n");
for (int i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
return 0;
}
在上面的代码中,bubbleSort
函数实现了冒泡排序算法,main
函数演示了如何使用这个函数对一个数组进行排序。在最外层的循环中,i
表示当前已经排好序的元素个数,内层的循环中,j
表示当前比较的两个相邻元素的索引。如果arr[j]
比arr[j+1]
大(或者小,取决于是升序还是降序排序),则交换它们的位置。
冒泡排序的时间复杂度为O(n^2),是一种比较低效的排序算法,不适合对大规模数据进行排序,但在学习排序算法的过程中具有一定的参考价值。
#include <stdio.h>
int main()
{
int i = 0;
int arr[] = {1,23,56,2,56,85,4,2,5,4,245};
int n = sizeof(arr)/sizeof(arr[0]);
for(i=0;i<n-1;i++){
for(int j=0;j<n-1-i;j++){
if(arr[j]>arr[j+1]){
arr[j]^=arr[j+1];
arr[j+1]^=arr[j];
arr[j]^=arr[j+1];
}
}
}
for(i=0;i<n;i++){
printf("%d ",arr[i]);
}
return 0;
}
这里我的两个值交换不严谨,因为数组内有相同值就会变成0,所以使用其他方式最好