C语言与数据结构基本知识点阐述笔记🚀
看目录之前点击这个链接{ https://github.com/FRANK0174/Simple_Note},其中还有许多关于C语言、数据结构的资料,并且拥有笔记软件安装包(Typora破解版),最后请在我的主页上点击⭐️支持一下吧。
Los, raus mit dir. Beweise, dass du besser bist als messi. ------Hans lefferts trainer der fußball-weltmeisterschaft 2014
文章目录
- C语言与数据结构基本知识点阐述笔记:rocket:
- 数据类型
- 语句的分类
- C格式说明符
- 流程图表示算法
- 伪代码
- 进制转换
- 算术移位
- 输入输出函数
- 字符与字符串
- 局部变量与全局变量
- typedef关键字的使用
- 指针
- 指针的基本分类
- 字符串与字符指针
- 函数(Function)的意义及其细节
- C库函数的基本实现
- 二维数组
- C各区的定义
- 为什么将堆(Heap)和栈(Stack)分开:
- register关键字
- C无内存常量
- 数组函数的使用
- main函数的参数
- 抽象状态机的定义和代码
- 递归
- C代码运行的细节:
- 文件
- 预处理器
- define关键字的使用
- 预定义宏
- 错误输出
- C可变参数
- C内存管理
- System()函数详解
- C枚举
- C函数指针和回调函数
- 位域
- 共用体
- C结构体
- . 与->在c语言中的区别
- 左值和右值的区别
- 布尔运算
- 原码和反码及补码的存在意义
- 时间复杂度
- 算法的意义及其简单应用
- 数据结构简单代码
- 查找和排序
- 你最爱的题库:heart_eyes:([【C语言】机试100题及代码答案(上)_c语言编程100题及答案-CSDN博客](https://blog.csdn.net/isak233/article/details/128767745?ops_request_misc=%7B%22request%5Fid%22%3A%22170651775316800182736948%22%2C%22scm%22%3A%2220140713.130102334..%22%7D&request_id=170651775316800182736948&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~top_click~default-5-128767745-null-null.142^v99^pc_search_result_base7&utm_term=c语言题库&spm=1018.2226.3001.4187))
数据类型
1.基本数据类型:
-
整数类型(Integer Types):
int
:通常为32位整数(取决于编译器和系统)。short
:短整数,通常为16位。long
:长整数,通常为32位,也可能是64位。long long
:长长整数,通常为64位。
int a = 10; short b = 32767; long c = 100000L; long long d = 123456789012345LL;
-
浮点类型(Floating-Point Types):
float
:单精度浮点数。double
:双精度浮点数,通常为64位。long double
:长双精度浮点数,大小可能超过double
。
float x = 3.14f; double y = 123.456; long double z = 3.141592653589793238L;
2.构造类型(Derived Types):
-
数组(Arrays):
- 由相同类型的元素组成的集合。
int numbers[5] = {1, 2, 3, 4, 5};
-
结构体(Structures):
- 允许将不同类型的数据组合在一起。
struct Point { int x; int y; }; struct Point p1 = {10, 20};
-
共用体(Unions):
- 允许使用相同的内存位置存储不同类型的数据。
union Data { int i; float f; char str[20]; }; union Data data;
-
枚举(Enumerations):
- 用于定义用户定义的枚举类型。
enum Color { RED, GREEN, BLUE }; enum Color selectedColor = BLUE;
3.空类型(Void Type):
-
void
:- 表示没有数据类型。
void myFunction() { // 函数没有返回值 }
4.指针类型(Pointer Types):
-
指针(Pointers):
- 存储其他变量的内存地址。
int num = 42; int *ptr = # // ptr存储num的地址
以上是C语言中常见的数据类型。在实际编程中,选择合适的数据类型取决于程序的需求和所处理的数据。C语言的数据类型系统提供了足够的灵活性,使得程序员能够高效地管理内存和处理不同类型的数据。
语句的分类
在编程语言中,语句是一组指令,用于执行特定的操作。不同类型的语句执行不同的任务。以下是常见的几种语句类型:
1.赋值语句:
将一个值赋给变量。例如,x = 5;
2.条件语句:
根据条件选择性地执行不同的代码块。例如,if
语句和switch
语句。
if (condition) {
// code block executed if condition is true
} else {
// code block executed if condition is false
}
switch (expression) {
case value1:
// code block executed if expression equals value1
break;
case value2:
// code block executed if expression equals value2
break;
// ...
default:
// code block executed if expression doesn't match any case
}
3.循环语句:
重复执行一段代码,直到满足特定条件。例如,for
、while
、do-while
循环。
for (int i = 0; i < 5; i++) {
// code block executed 5 times
}
while (condition) {
// code block executed while condition is true
}
do {
// code block executed at least once, then repeated while condition is true
} while (condition);
4.函数调用语句:
调用一个函数执行特定任务。例如,printf("Hello, World!");
5.跳转语句:
控制程序执行流程的语句。例如,break
、continue
、return
。
break; // 结束循环或 switch 语句的执行
continue; // 结束当前循环的本次迭代,继续下一次迭代
return 0; // 从函数中返回值,结束函数的执行
6.异常处理语句(部分语言支持):
处理运行时错误或异常的语句。例如,try
、catch
、throw
(在一些面向对象的语言中)。
try {
// code that might throw an exception
} catch (Exception e) {
// handle the exception
}
这些是一些常见的语句类型,不同的编程语言可能支持不同的语句。每种语句类型都有其特定的语法和用法。
C格式说明符
以下是C语言中的格式说明符的详细说明,包括一些不太常见的格式说明符:
%d
:用于整数(十进制)的格式说明符。可以用于int
类型。- 示例:
printf("%d", 42);
,将输出整数42
。
- 示例:
%ld
:用于长整数(长整型,十进制)的格式说明符。通常用于long
类型。- 示例:
printf("%ld", 10000000000L);
- 示例:
%lld
:用于长长整数(长长整型,十进制)的格式说明符。通常用于long long
类型。- 示例:
printf("%lld", 100000000000000LL);
- 示例:
%f
:用于浮点数的格式说明符。通常用于float
和double
类型。- 示例:
printf("%f", 3.14159);
- 示例:
%lf
:用于双精度浮点数(双精度浮点型)的格式说明符。通常用于double
类型。- 示例:
printf("%lf", 3.14159265359);
- 示例:
%c
:用于字符的格式说明符。通常用于char
类型。- 示例:
printf("%c", 'A');
- 示例:
%s
:用于字符串的格式说明符。通常用于字符数组(字符串)。- 示例:
printf("%s", "Hello, World!");
- 示例:
%x
:用于整数的十六进制格式。通常用于int
类型。- 示例:
printf("%x", 255);
,将输出十六进制值ff
。
- 示例:
%X
:与%x
类似,但输出大写字母的十六进制值。%o
:用于整数的八进制格式。通常用于int
类型。- 示例:
printf("%o", 64);
,将输出八进制值100
。
- 示例:
%u
:用于无符号整数的格式说明符。通常用于unsigned int
类型。- 示例:
printf("%u", 12345);
- 示例:
%p
:用于指针的格式说明符。通常用于指向任何数据类型的指针。- 示例:
printf("%p", &variable);
,将输出指向变量variable
的内存地址。
- 示例:
%e
:用于科学计数法的浮点数表示。- 示例:
printf("%e", 1.23e-4);
,将输出1.230000e-04
。
- 示例:
%E
:与%e
类似,但输出大写字母的指数。%g
:用于自动选择%f
或%e
来表示浮点数,以较短的形式表示。- 示例:
printf("%g", 0.000012345);
,可能输出1.2345e-05
或0.000012345
,取决于精度。
- 示例:
%G
:与%g
类似,但输出大写字母的指数。%%
:用于打印百分号字符。- 示例:
printf("The discount is 20%%.");
,将输出 “The discount is 20%.”
- 示例:
这些是C语言中的常见格式说明符,可以根据需要将它们与修饰符(如宽度、精度等)结合使用,以更精细地控制格式化的输出或输入。不同的数据类型需要相应的格式说明符,确保在使用 scanf
时提供正确的地址来存储输入的数据。
%#x
是C语言中的格式说明符,通常用于在输出时以十六进制格式打印整数。这个格式说明符的作用是在输出的十六进制数字前添加前缀 0x
,以明确表明数字是十六进制的。以下是一个示例:
int num = 255;
printf("%#x", num);
在这个示例中,%#x
将以十六进制格式打印整数 num
,输出将是 0xff
。前缀 0x
表示这是一个十六进制值。
%#x
格式说明符通常用于增加可读性,特别是在调试和输出十六进制数据时非常有用。如果省略 #
符号,输出将不包含前缀 0x
。
流程图表示算法
1.顺序结构
如下图所示,ABC结构为顺序结构,先执行A框,然后执行B框,其次执行C框,是最简单的一种基本结构。
2.选择结构
选择结构又称为选取结构或分支结构。如下图所示,此结构中必包含一个判断框,根据给定的条件P是否成立而选择执行A框或B框。
3.循环结构
-
当型(while型)循环结构
-
直到型(until型)循环结构
判断这两种循环类型的的方法:“先判断,后执行”为当型循环,典型为while语句。“先执行,后判断”为直到型循环,典型为do…while语句。
伪代码
伪代码(Pseudocode)是一种高级描述性的编程语言,用于描述算法或程序的逻辑结构,而不涉及特定的编程语法。它是一种自然语言和编程语言的混合,用于表示算法的思路和逻辑,而不关心具体的编程语法规则。伪代码通常在程序设计的早期阶段使用,帮助程序员和团队理清思路,确定解决问题的方法。
以下是一些关于伪代码的使用方法的说明:
1. 自然语言结构:
Algorithm:
Initialize total to 0
For each item in the list:
Add item to total
Display total
2. 无需关注具体语法:
- 伪代码不需要关心具体的编程语法规则,因此可以用简单的方式表达算法的核心思想。
Algorithm:
Set counter to 1
While counter is less than or equal to 10:
Display "Hello, World!"
Increment counter by 1
3. 结构化描述:
- 伪代码可以采用结构化的方式描述算法的逻辑结构,包括顺序、选择和循环等结构。
Algorithm:
Input a number
If the number is even:
Display "The number is even"
Else:
Display "The number is odd"
4. 模块化设计:
- 伪代码支持模块化设计,允许程序员将算法划分为多个独立的模块,以提高代码的可读性和可维护性。
Algorithm CalculateArea:
Input radius
Set area to π * radius^2
Return area
Algorithm Main:
Call CalculateArea with radius=5 and store result in result
Display "The area is " + result
5. 可读性和可理解性:
- 伪代码的主要优势在于其高度的可读性和可理解性,有助于团队协作和问题解决的讨论。
Algorithm:
For each student in the class:
If student's score is greater than 90:
Display "Excellent"
Else if student's score is greater than 70:
Display "Good"
Else:
Display "Needs improvement"
6. 算法设计与讨论:
- 伪代码在算法设计和讨论中扮演着重要角色,可以帮助团队成员更容易地理解和沟通算法的思路。
Algorithm BubbleSort:
For i from 1 to n:
For j from 0 to n - i - 1:
If list[j] > list[j + 1]:
Swap list[j] and list[j + 1]
总体而言,伪代码是一种灵活且简单的工具,用于描述算法的思路和逻辑。通过使用伪代码,程序员可以更轻松地理清复杂算法的步骤,并在团队协作中更好地分享和交流设计思想。
进制转换
1.进制的权(The right to base)
- 二进制的权是1,2,4,8,16…位是底数为2的n+1次方幂
- 十六进制的权是16,256,4096…,每前进一位是底数为16的n+1次方幂
- 八进制的权是8,64,512 …,每前进一位是底数为8的n+1次方幂
2.以十进制为中心的转换
-
其他进制–>十进制
方法:按权展开求和法
-
十进制–>其他进制
方法:除基取余逆读法
-
说明:十进制数除以基数(想转成几进制基数就是几)得到商和余数(是整数),继续用得到的商除以基数,得到商和余数,直到商为0时为止。最后倒序读取余数作为结果。
3.以二进制(Decimalism 缩写:DEC)为中心进行转换
说明:每4位二进制可以表示1位十六进制(0000-1111,0-15正好是十六进制的系数范围);
-
二进制–>十六进制(
hexadecimal or HEX
)方法:4合1
说明:从后向前将每4位二进制数分为1组(如果最左侧一组不够4位,可以在前面补0);计算每组二进制对应的十六进制结果;按顺序读取结果;
-
十六进制–>二进制
方法:1分4
说明:将每位十六进制数据,拆分成4位二进制。
-
二进制–>八进制(
octonary number system or OCT
)方法:3合1
说明:每3位二进制合成1位八进制 。(具体算法与二–>十六相同)
-
八进制–>二进制
方法:1分3
说明:每位八进制数拆分成3位二进制;
问题:八进制和十六进制之间相互转换。
-
中间可以使用二进制(或十进制)作为桥梁转换。
算术移位
当涉及到位移操作时,了解算术移位和逻辑移位的细节非常重要,尤其是在处理有符号整数时。下面将更详细地解释这两种操作的工作方式和使用场景。
1.算术左移 (Arithmetic Left Shift):
-
算术左移将二进制数向左移动指定的位数。移位后,最左侧的位将被舍弃,右侧将填充零。
-
算术左移等效于将整数乘以2的幂。每次左移一位,相当于乘以2。
1 v a l u e 左移时 v a l u e ∗ 2 n v a l u e 右移时 v a l u e 2 n {1} value左移时value*2^n\\ value右移时\frac{value}{2^n}\\ 1value左移时value∗2nvalue右移时2nvalue
2.算术右移 (Arithmetic Right Shift):
- 算术右移将二进制数向右移动指定的位数。移位后,最右侧的位将被舍弃,而左侧将填充与原最高位相同的位(即符号位)。如果原数是正数,那么填充零;如果原数是负数,那么填充一位1。
- 算术右移通常用于保持整数的符号。它允许负数继续保持负数,而正数保持正数。
3.逻辑左移 (Logical Left Shift):
- 逻辑左移与算术左移类似,将二进制数向左移动指定的位数。移位后,最左侧的位被丢弃,右侧填充零。
- 逻辑左移通常用于无符号整数,因为它不关心符号。
4.逻辑右移 (Logical Right Shift):
- 逻辑右移将二进制数向右移动指定的位数。移位后,最右侧的位被丢弃,左侧填充零。无论原数是正数还是负数,都填充零。
- 逻辑右移通常用于无符号整数。
在算术移位中移位的长短,会导致最高位发生变化。 然而在逻辑移位中,最高位始终不会发生变化,所以适用无符号整数。 在算术移位中移位的长短,会导致最高位发生变化。\\然而在逻辑移位中,最高位始终不会发生变化,所以适用无符号整数。 在算术移位中移位的长短,会导致最高位发生变化。然而在逻辑移位中,最高位始终不会发生变化,所以适用无符号整数。
需要特别注意的是,不同的编程语言和硬件平台可能对算术移位和逻辑移位的行为有所不同,因此在使用时要谨慎,确保了解特定环境下的移位行为。通常,算术移位在处理有符号整数时更为常见,而逻辑移位在处理无符号整数和位操作时更常见。
5.算数移位的妙用
交换两数值
a=a^b;b=b^a;a=a^b;
/*eg:a=5,b=9. a到a2名称上的变化是因为区分它们的状态,其他命名同理
第一步(a=a^b):0101->a=5,1001->b=9,1100->a1=12; a=5,b=9
第二步(b=b^a):1001->b=9,1100->a1=12,0101->b1=5;
第三步(a=a^b):1100->a1=12,0101->b1=5,1001->a2=9; a2=9,b1=5
*/
输入输出函数
在C语言中,有一些标准的输入输出函数,它们用于从用户获取输入和将数据输出到屏幕。以下是一些常见的C输入输出函数:
输入函数:
- scanf():
- 用于从标准输入(键盘)读取输入。
- 格式:scanf(“格式控制字符串”, &变量1, &变量2, …);
int num; scanf("%d", &num);
- getchar():
- 用于从标准输入读取一个字符。
- 返回读取的字符。
-
char ch; ch = getchar();
- gets():
- 用于从标准输入读取一个字符串。
- 不推荐使用,因为它没有边界检查,容易导致缓冲区溢出。
-
char str[100]; gets(str);
- fgets():
- 用于从指定的文件流读取一行字符串。
- 推荐用于读取字符串,因为它可以指定读取的最大字符数。
-
char str[100]; fgets(str, sizeof(str), stdin);
输出函数:
- printf():
- 用于将输出格式化为字符串并打印到标准输出(屏幕)。
-
int num = 10; printf("The value of num is %d\n", num);
- putchar():
- 用于将一个字符写入到标准输出。
-
char ch = 'A'; putchar(ch);
- puts():
- 用于将字符串写入到标准输出。
-
char str[] = "Hello, World!"; puts(str);
- fputs():
- 用于将字符串写入到指定的文件流。
-
char str[] = "Hello, World!"; FILE *file = fopen("output.txt", "w"); if (file != NULL) { fputs(str, file); fclose(file); }
这些是一些基本的C语言输入输出函数,它们为用户提供了从标准输入读取数据和将数据输出到屏幕或文件的功能。在实际编程中,可以根据需要选择合适的函数来完成输入输出任务。
字符与字符串
在C语言中,字符和字符串是重要的数据类型,它们用于表示文本信息。以下是对C语言中字符和字符串的详细说明:
1.字符(Char):
在C语言中,char
数据类型用于表示字符。字符是一个字母、数字或符号,用单引号括起来。
char myChar = 'A';
- 字符常量: 字符常量是用单引号括起来的单个字符。
char ch = 'X';
- 字符数组: 字符数组是由字符组成的数组,用于存储字符串。
char myString[6] = "Hello"; // 字符数组,最后一个位置为字符串结束符 '\0'
2. 字符串(String):
在C语言中,字符串实际上是一个字符数组,以空字符 '\0'
结尾。
char myString[] = "Hello";
- 字符串常量: 字符串常量是由字符组成的常量,用双引号括起来。
const char* greeting = "Hello, World!";
- 字符串函数: C语言提供了许多处理字符串的库函数,如
strlen
(获取字符串长度)、strcpy
(拷贝字符串)、strcat
(连接字符串)等。
#include <string.h>
int main() {
char str1[20] = "Hello";
char str2[] = "World";
// 连接字符串
strcat(str1, str2);
// 输出:HelloWorld
printf("%s\n", str1);
// 获取字符串长度
int length = strlen(str1);
// 输出:Length: 10
printf("Length: %d\n", length);
return 0;
}
- 输入输出函数: 使用
printf
和scanf
函数来进行字符串的输入输出。
#include <stdio.h>
int main() {
char name[20];
// 输入字符串
printf("Enter your name: ");
scanf("%s", name);
// 输出字符串
printf("Hello, %s!\n", name);
return 0;
}
3. 注意事项:
- 字符串在内存中是连续存储的字符数组,以
'\0'
结尾。 - 字符串处理时需要小心数组越界的问题。
- 字符串函数通常需要使用
<string.h>
头文件。 - 在C语言中,字符串是不可变的,即字符串中的字符不能被直接修改。需要使用字符数组(
char array[]
)来实现可变字符串。
C语言中字符和字符串的处理是编程中常见的任务,对它们的理解和熟练使用是进行文本处理的关键。
//截取字符串
#include<stdio.h>
int main(){
int hoge=0x12345678;
unsigned char* hoge_p=(unsigned char*)&hoge;
printf("%x\n",hoge_p[0]);
printf("%x\n",hoge_p[1]);
printf("%x\n",hoge_p[2]);
printf("%x\n",hoge_p[3]);
printf("%x\n",hoge_p[4]);
return 0;
}
局部变量与全局变量
1.局部变量
在一个函数内定义的变量只能在本函数内生效。同样,在一条复合语句内定义的变量也只在本复合语句内生效。以上这些被称为局部变量。
char a2(int a,int b){
int a1,b1;
{
int a2,b2; //a2,b2 只在此区域生效,3至5行代码称为复合语句。
}
}
char a3{
//现在,所有在函数a2里定义的变量都无法生效。
}
2.全局变量
在函数体之外定义的变量为全局变量,全局变量可以在本源文件内所有函数共用,定义有效范围从定义位置至本源文件结束。
#include<stdio.h>
int a = 10;
int arr(int a) {
return a;
}
int main() {
printf("%d", arr(a));
//printf("%d", arr(b)); //a与b同样是全局变量,但是因为有效范围的不同,b不能被使用,
}
int b = 10;
typedef关键字的使用
typedef
是C和C++等编程语言中的一个关键字,用于创建新的数据类型别名,使代码更易读和理解。typedef
允许程序员定义自己的数据类型名称,以简化代码和提高可维护性。以下是typedef
关键字的使用示例:
1.基本数据类型的别名:
typedef int MyInt; // 创建一个整数的别名
MyInt x = 42;
这里,MyInt
成为 int
的别名,可以使用 MyInt
来声明变量,这使得代码更具可读性。
2.结构体的别名:
typedef struct {
int x;
int y;
} Point; // 创建一个结构体的别名
Point p1;
p1.x = 5;
p1.y = 10;
这里,Point
成为了一个结构体的别名,使得声明和使用结构体更加简洁。
3.函数指针的别名:
typedef int (*MathFunction)(int, int); // 创建一个函数指针类型的别名
int add(int a, int b) {
return a + b;
}
MathFunction operation = add;
int result = operation(3, 4);
在这个示例中,MathFunction
成为一个函数指针类型的别名,它可以指向具有相同签名的函数。
4.枚举的别名:
typedef enum {
RED,
GREEN,
BLUE
} Color; // 创建一个枚举类型的别名
Color favoriteColor = GREEN;
这里,Color
成为一个枚举类型的别名,使得使用枚举值更加清晰。
5.指针类型的别名:
cCopy codetypedef int* IntPtr; // 创建一个整数指针的别名
int num = 42;
IntPtr ptr = #
IntPtr
成为指向整数的指针的别名,可以用于声明指向整数的指针变量
typedef
关键字可以显著提高代码的可读性,特别是在处理复杂的数据类型或在多个地方重复使用相同类型时。它还可以用于封装库中的数据类型,使其更具可移植性,因为库开发者可以更轻松地更改实际数据类型而不会影响用户的代码。
指针
C语言中的指针是一种非常重要的数据类型,它用于存储内存地址并提供了对内存中数据的直接访问。指针是C语言的关键特性之一,能够有效地进行内存管理和数据操作。下面是关于C语言指针的详细介绍:
1.指针的声明和定义:
- 使用
*
运算符来声明指针变量,例如:int *ptr;
表示声明了一个指向整数的指针。 - 指针变量需要初始化为合法的内存地址,通常可以使用
&
运算符取得一个变量的地址,并将其赋值给指针变量。
2.指针的解引用:
- 使用
*
运算符对指针进行解引用,以获取指针所指向的内存中的值。例如:int x = *ptr;
会将指针ptr
所指向的整数值赋给变量x
。
3.指针的运算:
- 指针可以进行算术运算,如加法和减法。这允许你在内存中导航,访问数组元素等。例如:
ptr++
会将指针向前移动到下一个元素。 - 指针的加法和减法操作都是基于数据类型的大小。例如,如果
ptr
是一个int
指针,则ptr++
会将指针移动到下一个整数的位置。
4.指针与数组:
- 数组名本质上是一个指向数组第一个元素的指针。例如:
int arr[5]; int *ptr = arr;
,ptr
指向数组arr
的第一个元素。 - 可以使用指针来遍历数组元素,而不必使用数组下标。例如:
for (int i = 0; i < 5; i++) { printf("%d\n", *(ptr + i)); }
。
5.指针与函数:
- 指针可以用于传递函数参数,允许函数修改传递给它的变量的值。这称为通过引用传递。例如:
void modifyValue(int *x) { *x = 10; }
。
6.指针与动态内存分配:
- 使用
malloc
、calloc
或realloc
函数来动态分配内存,并返回一个指向分配内存的指针。必须记得释放这些内存,以避免内存泄漏。例如:int *arr = (int *)malloc(sizeof(int) * 10);
。
7.指针的 NULL 值:
- 可以使用
NULL
宏来表示指针变量的空值。这对于检查指针是否有效非常有用。例如:int *ptr = NULL;
。
8.指针的安全性和悬空指针:
- 未初始化的指针或已释放的指针可能会成为悬空指针,访问悬空指针会导致不可预测的行为,甚至导致程序崩溃。因此,指针的安全使用非常重要。
C语言指针是一个非常强大的工具,但也需要小心使用,以确保内存安全性和程序稳定性。正确使用指针可以提高代码的性能和灵活性,但不正确的使用可能会导致难以调试的错误。因此,在使用指针时要格外小心。
指针的基本分类
C语言中指针是一种强大而灵活的工具,可以进行多种操作。以下是一些常见和不常见的C指针用法的代码示例:
1. 基本指针用法:
#include <stdio.h>
int main() {
int x = 10;
int *ptr; // 定义整型指针
ptr = &x; // 将指针指向变量x
printf("Value of x: %d\n", x);
printf("Value using pointer: %d\n", *ptr);
return 0;
}
2. 指针数组:
#include <stdio.h>
int main() {
int arr[] = {1, 2, 3, 4, 5};
int *ptrArr[5]; // 定义整型指针数组
for (int i = 0; i < 5; i++) {
ptrArr[i] = &arr[i]; // 每个指针指向数组的一个元素
printf("Value at index %d: %d\n", i, *ptrArr[i]);
}
return 0;
}
3. 指向函数的指针:
#include <stdio.h>
int add(int a, int b) {
return a + b;
}
int main() {
int (*ptr)(int, int); // 定义指向函数的指针
ptr = &add; // 将指针指向函数
int result = (*ptr)(5, 3); // 通过指针调用函数
printf("Result of addition: %d\n", result);
return 0;
}
4. 指针的指针:
#include <stdio.h>
int main() {
int x = 10;
int *ptr1 = &x;
int **ptr2 = &ptr1; // 定义指向指针的指针
printf("Value of x: %d\n", x);
printf("Value using single pointer: %d\n", *ptr1);
printf("Value using double pointer: %d\n", **ptr2);
return 0;
}
5. const 指针:
#include <stdio.h>
int main() {
int x = 10;
const int *ptr = &x; // 定义指向常量的指针
// *ptr = 20; // 错误,不能通过指针修改常量的值
x = 20; // 可以直接修改变量的值
printf("Value using pointer: %d\n", *ptr);
return 0;
}
6. void 指针:
#include <stdio.h>
int main() {
int x = 10;
float y = 3.14;
void *genericPtr; // 定义void指针,可以指向任何类型
genericPtr = &x;
printf("Value of x: %d\n", *(int *)genericPtr);
genericPtr = &y;
printf("Value of y: %f\n", *(float *)genericPtr);
return 0;
}
这些示例涵盖了一些常见和不常见的C指针用法,包括基本指针、指针数组、指向函数的指针、指针的指针、const 指针和void 指针。每种用法都展示了指针在不同场景下的灵活性和实用性。
字符串与字符指针
在C语言中,字符串和字符指针是相关但不同的概念。下面详细解释它们之间的区别:
1. 字符串:
-
定义: 字符串是一串以空字符 ‘\0’ 结尾的字符序列。C语言没有专门的字符串类型,通常使用字符数组来表示字符串。
char myString[] = "Hello, World!";
-
特点: 字符串以空字符 ‘\0’ 结尾,表示字符串的结束。它是一种约定,允许C语言的字符串处理函数知道字符串在哪里结束。
-
操作: C语言提供了一系列处理字符串的库函数,如
strlen
(计算字符串长度)、strcpy
(拷贝字符串)、strcat
(连接字符串)等。
2. 字符指针:
-
定义: 字符指针是指向字符的指针。它可以指向一个字符,也可以指向字符数组,因为数组的名称可以视为指向数组第一个元素的指针。
char myChar = 'A'; char *charPtr = &myChar; char myString[] = "Hello"; char *strPtr = myString;
-
特点: 字符指针存储某个内存地址,该地址上存放一个字符的值。通过指针运算,可以遍历字符串的每个字符。
-
操作: 字符指针可以用于访问字符串中的每个字符,也可以通过指针运算实现字符串操作。例如,通过递增指针来遍历字符串。
3. 区别:
-
结尾标志: 字符串以空字符 ‘\0’ 结尾,而字符指针只是一个指向字符的地址,不一定指向以空字符结尾的字符串。
-
初始化: 字符串可以通过字符数组直接初始化,而字符指针通常需要先指向一个合法的内存地址,然后再将其初始化为字符数组的地址。
char myString[] = "Hello"; // 字符串的直接初始化 char *strPtr = myString; // 字符指针指向字符数组的地址
-
指针运算: 字符指针可以进行指针运算,让指针指向下一个字符,而字符串本身不能进行类似的指针运算。
char *strPtr = "Hello"; char firstChar = *strPtr; // 获取第一个字符 char secondChar = *(strPtr + 1); // 获取第二个字符
总体而言,字符串是字符数组,而字符指针是指向字符的指针。在处理字符串时,经常使用字符数组,而字符指针则更多用于遍历和操作字符串的字符。理解它们之间的区别有助于更有效地使用C语言进行字符串处理。
函数(Function)的意义及其细节
1.意义:
main函数是程序的初始函数,如果将所有代码写入主函数中,那么main函数将会变得特别繁杂,导致可维护行降低,并且如果同样功能的代码反复使用,代码的可读性也会变得特别低。为了消除这种弊端,函数理念应运而生,提倡模块化程序设计,在使用时,只需要组装,没必要反反复复的重新写相同的代码。
2.要求
Function单词的本意就是“功能,职责”,所以函数的规范就是一个函数实现一个特定的功能,并且函数的名称要反映函数的功能。
3.注意事项
1.函数的构造须由函数类型,函数名称,函数参数,函数体,函数返回值组成,其中函数参数和函数返回值可以视情况而定。
2.函数应“先定义,后使用”原则,并且如果函数体在主函数(同一源文件内)之后,如果使用必须在主函数提前声明。
3.函数的形参和实参,实参是主函数调用函数时的传递的参数,而形参是函数被使用时函数接受到的参数,函数参数的传递是通过拷贝完成的的,并且函数使用完之后销毁,所以有实参和形参之分。
4.函数的调用可以作为另一个函数的实参
#include <stdio.h>
//三数比大小
int swap(int a, int b) {
if (a > b)
return a;
return b;
}
int main() {
printf("%d", swap(9, swap(8, 3)));
}
5.函数的执行是通过内存空间内的栈完成的,通过栈顶指针和栈底指针的配合使用,非常完美的将代码的结构性,空间性展现出来。
C库函数的基本实现
1.strcpy函数
char* strcpy(char* dst, const char* src)
{
if ((dst == NULL) || (src == NULL))
return NULL;
char* ret = dst;
while ((*dst++ = *src++) != '\0');
return ret;
}
2.strlen函数
int strlen(char *Str) {
int index = 0;
while (*Str++ != '\0') {
index++;
}
return index;
}
二维数组
二维数组是一种特殊的数组,它可以看作是一维数组的数组。它是在行和列的两个维度上组织数据的一种方式。在C语言中,二维数组的声明和使用有一些特殊之处。以下是对二维数组的详细介绍:
1. 声明和初始化:
int matrix[3][4]; // 声明一个3行4列的二维整型数组
// 初始化二维数组
int matrix2[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
2. 访问元素:
二维数组的元素通过行和列的索引进行访问。
int value = matrix[1][2]; // 获取第2行第3列的元素值
3. 内存布局:
二维数组在内存中是按行存储的。例如,一个3行4列的整型数组在内存中的布局是连续存储的 3 行 * 4 列 = 12 个整数。
4. 多维数组的概念:
除了二维数组,C语言还支持更高维度的数组。例如,三维数组可以看作是一个二维数组的数组。
int threeDArray[2][3][4]; // 一个2行3列4深度的三维数组
5. 传递给函数:
在函数参数中传递二维数组时,需要指定列的大小。
void printMatrix(int rows, int cols, int matrix[rows][cols]) {
for (int i = 0; i < rows; i++) {
for (int j = 0; j < cols; j++) {
printf("%d ", matrix[i][j]);
}
printf("\n");
}
}
int main() {
int matrix[2][3] = {
{1, 2, 3},
{4, 5, 6}
};
printMatrix(2, 3, matrix);
return 0;
}
6. 动态分配内存:
可以使用动态内存分配函数(malloc
, calloc
, free
)来创建动态二维数组。
int rows = 3, cols = 4;
int **dynamicMatrix = (int **)malloc(rows * sizeof(int *));
for (int i = 0; i < rows; i++) {
dynamicMatrix[i] = (int *)malloc(cols * sizeof(int));
}
7. 注意事项:
- 二维数组的大小必须在编译时确定。
- 使用多维数组时,需要小心数组越界的问题。
int matrix[3][4];
matrix[3][4] = 10; // 越界访问,可能导致未定义行为
二维数组是在许多编程场景中广泛使用的数据结构,例如矩阵运算、图形处理等。熟练掌握二维数组的声明、初始化和使用方法对于进行复杂的数据操作非常重要。
C各区的定义
在C语言中,内存分为不同的区域,通常包括以下几个主要的区域,每个区域有其特定的用途和生命周期:
1.栈(Stack):
- 栈是用于存储函数调用和局部变量的内存区域。
- 每次函数被调用时,该函数的局部变量都会被分配到栈上。
- 栈的特点是后进先出(LIFO),即最后进栈的变量最先出栈。
- 局部变量在函数退出时自动销毁,不需要手动释放。
- 栈的内存分配和释放是自动管理的,通常由编译器和操作系统负责。
2.堆(Heap):
- 堆是用于动态分配内存的内存区域,通常用于存储动态数据结构,如链表、树和对象。
- 堆内存需要手动分配和释放,程序员负责管理其生命周期。
- 堆内存通常比栈大,不受固定大小的限制。
- 堆的分配和释放由程序员通过函数如
malloc
、calloc
、realloc
和free
来控制。
3.全局/静态区(Global/Static Area):
- 全局区用于存储全局变量和静态变量。
- 全局变量在程序启动时创建,并在程序退出时销毁。
- 静态变量在其作用域内保留其值,并且不会被销毁。
- 全局变量和静态变量在整个程序的生命周期内存在。
4.文字常量区(Text/Code Segment):
- 文字常量区存储程序的代码和常量数据。
- 通常,程序代码和常量字符串等在这个区域中,不可修改。
- 文字常量区的数据在程序运行期间不可更改。
5.常量区(Constant Area):
- 常量区存储不可更改的常量数据,如字符串常量。
- 这些数据在程序运行期间不可修改。
6.堆栈区(BSS,Block Started by Symbol):
- BSS区用于存储未初始化的全局变量和静态变量。
- 这些变量在程序启动时被初始化为零或空,不需要显式初始化。
7.寄存器(Register):
- 寄存器是CPU内部的内存区域,用于存储临时数据和快速访问变量。
- 程序员通常无法直接控制寄存器,编译器会自动优化变量的分配和访问。
不同的编译器、操作系统和平台可能会有不同的内存管理机制和命名约定,但这些是C语言通用的内存区域。了解这些区域的存在和基本特征对于正确管理内存和避免内存泄漏和错误非常重要。在C语言中,程序员通常需要手动管理堆内存,而栈和全局变量的管理由编译器和运行时系统自动处理。
为什么将堆(Heap)和栈(Stack)分开:
1.内存管理灵活性:
- 堆和栈的分离允许程序员根据需要灵活地分配和释放内存。栈上的内存分配和释放是自动的,而堆上的内存分配和释放由程序员手动控制。
- 堆内存的手动管理使程序能够在运行时动态地分配和释放内存,这对于处理不定数量和大小的数据结构非常有用。
2.局部变量的生命周期管理:
- 栈上的内存用于存储局部变量,这些变量的生命周期与函数调用的开始和结束直接相关。当函数退出时,栈上的局部变量自动被销毁,无需额外的操作。
- 这种自动管理有助于避免内存泄漏和资源泄漏。
3.栈的高效性:
- 栈的内存分配和释放非常高效,因为它是一个固定大小的内存区域,仅需要移动栈指针即可分配和释放内存。
- 栈上的数据结构通常具有更快的访问速度,因为栈上的数据在内存中是紧凑排列的,而且局部性原理使得访问局部变量更加高效。
4.堆的灵活性和扩展性:
- 堆允许动态分配和释放内存,因此可以处理不定数量和大小的数据。这使得堆非常适合存储动态数据结构,如链表、树和动态数组。
- 堆上的内存可以在程序运行时根据需求进行扩展,而栈的大小通常是固定的。
5.安全性和稳定性:
- 栈的自动管理有助于避免许多内存相关的错误,如空指针引用、内存泄漏和越界访问。局部变量的生命周期受到严格控制,不容易出现问题。
- 堆上的内存管理需要更多的注意和负担,但也提供了更大的灵活性,因此程序员需要小心确保内存的正确分配和释放。
总之,将堆和栈分开允许程序在内存管理、性能和安全性方面取得平衡。栈用于局部变量的自动管理和高效访问,而堆用于动态分配内存以满足程序的动态需求。这种分离使得C和类似的编程语言能够同时提供高性能、灵活性和可维护性。
register关键字
register
关键字是C语言中的一个关键字,用于向编译器建议将变量存储在寄存器中,以便提高对该变量的访问速度。但需要注意的是,register
关键字在现代编译器中的用法已经发生了变化,因此在实际编程中,很少使用 register
关键字来指示编译器将变量存储在寄存器中。以下是关于 register
关键字的一些条件和注意事项:
1.建议性关键字:
register
关键字是一个建议性关键字,意味着它向编译器提出请求,要求将变量存储在寄存器中,以提高访问速度。- 编译器不一定会遵循这个请求,因为它可能会根据编译器的优化策略和目标架构来决定是否将变量存储在寄存器中。
2.限制条件:
register
关键字只能用于自动变量(即局部变量),不能用于全局变量或静态变量。- 变量必须是可寻址的,也就是说,你不能使用
register
来声明指针或数组类型的变量。
3.不再常用:
- 在现代编译器中,
register
关键字的使用已经不再普遍,因为大多数编译器都能够根据上下文和优化策略自动选择是否将变量存储在寄存器中。 - 事实上,现代编译器通常会忽略
register
关键字,因此在代码中使用它通常没有实际效果。
4.编译器优化:
- 编译器通常会进行优化,自动选择最佳的寄存器分配策略,因此程序员不需要手动指定哪些变量应该存储在寄存器中
5.register关键字使用的条件
- register变量必须是能被CPU所接受的类型。
- 这通常意味着register变量必须是一个单个的值,并且长度应该小于或者等于整型的长度。不过,有些机器的寄存器也能存放浮点数。
- 因为register变量可能不存放在内存中,所以不能用“&”来获取register变量的地址。
- 只有局部自动变量和形式参数可以作为寄存器变量,其它(如全局变量)不行。
- 在调用一个函数时占用一些寄存器以存放寄存器变量的值,函数调用结束后释放寄存器。此后,在调用另外一个函数时又可以利用这些寄存器来存放该函数的寄存器变量。
- 局部静态变量不能定义为寄存器变量。不能写成:register static int a, b, c;
- 由于寄存器的数量有限(不同的CPU寄存器数目不一),不能定义任意多个寄存器变量,而且某些寄存器只能接受特定类型的数据(如指针和浮点数),因此真正起作用的register修饰符的数目和类型都依赖于运行程序的机器,而任何多余的register修饰符都将被编译程序所忽略。
总的来说,register
关键字在现代C编程中的用途非常有限,因为编译器通常能够更好地处理寄存器的分配。程序员可以信赖编译器的优化能力,而不需要手动干预变量的寄存器分配。因此,在大多数情况下,不建议使用 register
关键字,而应该依赖于编译器的自动优化。
C无内存常量
在C语言中,有一种特殊类型的变量叫做"常量",它们与普通变量不同,不会分配内存空间来存储值。C语言中有几种不同类型的常量,包括字面常量、符号常量和枚举常量,它们在编程中非常有用。以下是对这些常量的详细介绍:
1.字面常量(Literal Constants):
- 字面常量是编程语言中的固定值,直接写在程序代码中,不会分配内存空间。
- 例如,整数常量
42
、浮点数常量3.14
、字符常量'A'
和字符串常量"Hello, World!"
都属于字面常量。 - 字面常量用于提供固定的数据值,而不需要存储在内存中。
int x = 42; // 整数字面常量
float pi = 3.14; // 浮点数字面常量
char letter = 'A'; // 字符字面常量
char *str = "Hello, World!"; // 字符串字面常量
2.符号常量(Symbolic Constants):
- 符号常量是使用
#define
指令定义的,它们代表一个固定的值,通常用于增强代码的可读性和可维护性。 - 符号常量在编译时被替换为其具体的值,不会分配内存空间。
- 例如,以下代码定义了一个符号常量,用于表示圆周率:
#define PI 3.14
- 在代码中使用符号常量时,编译器会将所有出现的
PI
替换为3.14
。
3.枚举常量(Enum Constants):
- 枚举是一种用户定义的数据类型,它包含一组命名的常量值。这些常量值不会分配内存空间。
- 例如,以下代码定义了一个枚举类型
Color
,其中包含三个枚举常量:RED
、GREEN
和BLUE
。
enum Color {
RED,
GREEN,
BLUE
};
- 在代码中使用枚举常量时,它们仅代表其相应的整数值,不会分配额外的内存。
4.寄存器常量(Register constant)
-
寄存器常量是一种特殊类型的符号常量,通常用于向编译器建议将某个变量存储在CPU的寄存器中,以提高对该变量的访问速度。
-
以下是使用
register
关键字声明寄存器常量的示例: -
register int x; // 声明一个寄存器变量x
-
register
关键字用于向编译器提出建议,但不保证变量将存储在寄存器中。
5.普通定义的常量是有内存的
const 是内存分配的!
关键字const 并不能把变量变成常量!在一个符号前加上const 限定符只是表示这个符号 不能被赋值。也就是它的值对于这个符号来说是只读的,但它并不能防止通过程序的内部(甚至是外部)的方法来修改这个值。
#include <stdio.h>
void test(void)
{
static int a = 0;//static可以改变局部变量的生命周期(本质上是改变了变量的贮存类型)
a++;
printf("%d\n", a);
}
int main() {
for(int i=0;i<100;i++)
test();
system("pause");
return 0;
}
数组函数的使用
1.数组或字符串的长度:sizeof()
、strlen()
1、sizeof()
:返回所占总空间的字节数
(1)对于整型字符型数组
(2)对于整型或字符型指针
2、strlen()
:返回字符数组或字符串所占的字节数
(1)针对字符数组
(2)针对字符指针
sizeof(...)
是运算符,其值在编译时即计算好了,参数可以是数组、指针、类型、对象、函数等。它的功能是:获得保证能容纳实现所建立的最大对象的字节大小。由于在编译时计算,因此sizeof不能用来返回动态分配的内存空间的大小。
strlen(...)
是函数,要在运行时才能计算。参数必须是字符型指针(char)
。当数组名作为参数传入时,实际上数组就退化成指针了。它的功能是:返回字符串的长度。该字符串可能是自己定义的,也可能是内存中随机的,该函数实际完成的功能是从代表该字符串的第一个地址开始遍历,直到遇到结束符’\0’。返回的长度大小不包括’\0’。
strlen(char)
函数求的是字符串的实际长度,它求得方法是从开始到遇到第一个’\0’,如果你只定义没有给它赋初值,这个结果是不定的,它会从aa首地址一直找下去,直到遇到’\0’停止。
c/c++ strlen(str)
和str.length()
和str.size()
都可以求字符串长度,其中str.length()
和str.size()
是用于求string
类对象的成员函,strlen(str)
是用于求字符数组的长度,其参数是char。
eg:使用sizeof()
函数判定数组元素的个数
sizeof(ojects)/sizeof(ojects[0])
2.sizeof()
、strlen()
两者区别:
(1)sizeof
操作符的结果类型是size_t
,它在头文件中typedef
为unsigned int
类型,该类型保证能容纳实现所建立的最大对象的字节大小。
(2)sizeof
是运算符,strlen是函数。
(3)sizeof
可以用类型做参数,strlen只能用char做参数,且必须是以’‘\0’'结尾的。
sizeof
还可以用函数做参数,比如:
short f(){
///
};
printf("%d\n", sizeof(f()));
OUTPUT:sizeof(short),即2。
(4)数组做sizeof
的参数不退化,传递给strlen
就退化为指针了。
(5)大部分编译程序 在编译的时候就把sizeof
计算过了 是类型或是变量的长度。这就是sizeof(x)
可以用来定义数组维数的原因
char str[20]="0123456789";
int a=strlen(str); //a=10;
int b=sizeof(str); //而b=20;
(6)strlen
的结果要在运行的时候才能计算出来,用来计算字符串的长度,不是类型占内存的大小。
(7)sizeof
后如果是类型必须加括弧,如果是变量名可以不加括弧。这是因为sizeof
是个操作符不是个函数。
(8)当使用了一个结构类型或变量时, sizeof
返回实际的大小, 当使用一静态地空间数组, sizeof
返回全部数组的尺寸。 sizeof
操作符不能返回被动态分配的数组或外部的数组的尺寸
3.string
中length()
和size()
c++中的size()
和length()
没有区别
如:
string str="0123456789";
cout <<"str.length()="<<str.length()<<endl;//结果为10
cout <<"str.size()="<<str.size()<<endl;//结果为10
main函数的参数
argv[0]
存储程序的名称,argv[1]
是一个指向第一个命令行参数的指针,argv[n]
是最后一个参数。如果没有提供任何参数,argc
将为 1,否则,如果传递了一个参数,argc
将被设置为 2
int main(int argc,char *argv[],char *envp[])
1.不带参数的 main
函数:
int main() {
// 函数体
return 0;
}
在这种形式下,main
函数不接受任何参数。
2.带参数的 main
函数:
int main(int argc, char *argv[]) {
// 函数体
return 0;
}
这是 main
函数的标准形式,它接受两个参数:
-
int argc
: 表示命令行参数的数量(argument count)。 -
char *argv[]
: 表示命令行参数的数组(argument vector)。 -
argc
参数:argc
是一个整数,代表命令行参数的数量。- 它至少为1,因为程序的名称(通常是可执行文件的名称)被认为是第一个参数。
- 例如,如果你在命令行中输入
./my_program arg1 arg2
,那么argc
的值将是3。
-
argv
参数:argv
是一个字符指针数组,每个指针指向一个命令行参数的字符串。argv[0]
存储程序的名称,argv[1]
存储第一个参数,以此类推。- 注意,这些参数都以字符串形式存储,即使它们可能是数字或其他类型。
-
示例:
int main(int argc, char *argv[]) { printf("Program name: %s\n", argv[0]); // 打印程序的名称 printf("Number of arguments: %d\n", argc - 1); // 打印参数的数量 // 打印每个参数 for (int i = 1; i < argc; i++) { printf("Argument %d: %s\n", i, argv[i]); } return 0; }
如果你运行这个程序并提供一些命令行参数,它将打印程序名称、参数数量以及每个参数的值。这种带参数的
main
函数形式允许程序从命令行接收输入,这在需要动态配置程序行为的情况下非常有用。
抽象状态机的定义和代码
状态机(State Machine)是一种抽象模型,用于描述对象在不同状态之间转换的行为。它是由一组状态、一组转移条件和一组动作组成。状态机分为有限状态机(Finite State Machine,FSM)和无限状态机(Infinite State Machine)两种类型。有限状态机有一个有限的状态集,而无限状态机的状态集可能是无限的。
1.有限状态机的基本组成部分:
-
状态(State): 描述系统的当前情况或阶段。在状态机中,可以定义一组离散的状态,如"开始"、“进行中”、"完成"等。
-
转移(Transition): 描述从一个状态到另一个状态的条件。转移可能有触发条件,当条件满足时,状态机会从一个状态切换到另一个状态。
-
动作(Action): 描述在状态转移时执行的动作或行为。可以在进入或退出某个状态时执行动作。
2.状态机的代码示例(使用C语言):
下面是一个简单的有限状态机的示例,模拟一个灯的状态变化。
#include <stdio.h>
// 状态枚举
enum State {
OFF,
ON,
BLINKING
};
// 事件枚举
enum Event {
TURN_ON,
TURN_OFF,
TIMER_EXPIRED
};
// 状态机结构体
struct StateMachine {
enum State currentState;
};
// 处理事件的函数
void handleEvent(struct StateMachine *machine, enum Event event) {
switch (event) {
case TURN_ON:
machine->currentState = ON;
printf("Light turned ON\n");
break;
case TURN_OFF:
machine->currentState = OFF;
printf("Light turned OFF\n");
break;
case TIMER_EXPIRED:
if (machine->currentState == ON) {
machine->currentState = BLINKING;
printf("Light is now BLINKING\n");
} else if (machine->currentState == BLINKING) {
machine->currentState = ON;
printf("Light is now ON\n");
}
break;
default:
printf("Unknown event\n");
}
}
int main() {
// 初始化状态机
struct StateMachine lightStateMachine = { OFF };
// 处理事件
handleEvent(&lightStateMachine, TURN_ON);
handleEvent(&lightStateMachine, TIMER_EXPIRED);
handleEvent(&lightStateMachine, TIMER_EXPIRED);
handleEvent(&lightStateMachine, TURN_OFF);
return 0;
}
这个示例模拟了一个灯的状态机,有三个状态(OFF、ON、BLINKING),并且定义了三个事件(TURN_ON、TURN_OFF、TIMER_EXPIRED)。在 handleEvent
函数中,根据当前状态和触发的事件,进行相应的状态转移和动作执行。
下面的示例模仿了数字电路,第一份代码有两个状态(0,1),第二份代码有五个不同的状态,可以完成一组数字的演变。
#include<stdio.h>
#include<unistd.h>
#define REGS_FOREACH(_) _(X) _(Y)
#define RUN_LOGIC X1 = !X && Y; \
Y1 = !X && !Y;
#define DEFINE(X) static int X, X##1;
#define UPDATE(X) X = X##1;
#define PRINT(X) printf(#X " = %d; ", X);
int main() {
REGS_FOREACH(DEFINE);
while (1) { // clock
RUN_LOGIC;
REGS_FOREACH(UPDATE);
putchar('\n'); sleep(1);
}
}
#include <stdio.h>
#include <unistd.h>
#define REGS_FOREACH(_) _(X) _(Y)
#define OUTS_FOREACH(_) _(A) _(B) _(C) _(D) _(E) _(F) _(G)
#define RUN_LOGIC X1 = !X && Y; \
Y1 = !X && !Y; \
A = (!X && !Y) || (X && !Y); \
B = 1; \
C = (!X && !Y) || (!X && Y); \
D = (!X && !Y) || (X && !Y); \
E = (!X && !Y) || (X && !Y); \
F = (!X && !Y); \
G = (X && !Y);
#define DEFINE(X) static int X, X##1;
#define UPDATE(X) X = X##1;
#define PRINT(X) printf(#X " = %d; ", X);
int main() {
REGS_FOREACH(DEFINE);
OUTS_FOREACH(DEFINE);
while (1) { // clock
RUN_LOGIC;
OUTS_FOREACH(PRINT);
REGS_FOREACH(UPDATE);
putchar('\n');
fflush(stdout);
sleep(1);
}
}
递归
递归是一种在计算机科学中常见的编程和问题解决技巧,它允许函数在执行过程中调用自身。递归函数是一个函数,它通过将问题分解成更小的子问题来解决复杂的问题,每个子问题都与原始问题具有相同的结构。下面是关于递归的详细介绍:
1.递归的基本原理:
-
基本情况(Base Case):递归函数必须定义一个或多个基本情况,即在这些情况下函数不再调用自身,而是直接返回一个结果。基本情况是递归终止的条件。
-
递归情况(Recursive Case):递归函数在非基本情况下调用自身,通常是将原始问题分解成一个或多个更小的子问题。每个子问题的解决方法通常与原始问题的解决方法相同,只不过问题规模更小。
-
递归链(Recursion Chain):递归链是递归函数调用自身的序列,每次调用都将问题规模减小,最终达到基本情况,然后逐步返回结果。
2.递归的示例:
递归可以应用于各种问题,以下是一些示例:
- 阶乘计算:计算一个正整数的阶乘。基本情况是当输入为1时,阶乘为1;递归情况是n的阶乘等于n乘以(n-1)的阶乘。
int factorial(int n) {
if (n == 1) {
return 1; // 基本情况
} else {
return n * factorial(n - 1); // 递归情况
}
}
- 斐波那契数列:计算斐波那契数列的第n个数。基本情况是n为0或1时,返回n;递归情况是第n个数等于第(n-1)和第(n-2)个数的和。
int fibonacci(int n) {
if (n == 0 || n == 1) {
return n; // 基本情况
} else {
return fibonacci(n - 1) + fibonacci(n - 2); // 递归情况
}
}
3.递归的优点与注意事项:
-
简洁性:递归可以使代码更简洁和易于理解,尤其是对于解决自相似性问题的情况。
-
问题分解:递归可以将大问题分解为小问题,每个小问题的解决方法相同,使得问题更容易解决。
-
注意堆栈溢出:递归可能导致堆栈溢出,因此必须确保递归链的深度不会太大。可以使用尾递归或迭代来减少堆栈的使用。
-
性能开销:递归函数的性能通常比迭代函数差,因为每个递归调用都会引入额外的函数调用开销和堆栈开销。
-
维护状态:递归可能需要维护递归状态,例如递归调用的参数和局部变量。这些状态在递归链中传递。
总之,递归是一种强大的编程技巧,它在解决自相似性问题和将问题分解为更小部分时非常有用。理解递归的基本原理以及合理地选择递归或迭代可以帮助你更好地解决各种问题。
4.递归的详细说明:
(1)在求f(n, other variables)的时候,你就默认f(n -1, other variables)已经被求出来了——至于怎么求的,这个是计算机通过回溯求出来的。
PS:这里用到了一种叫做栈(stack)的先进后出的数据结构,所以递归输出的答案一般是自下而上的。
(2)递归和二叉树是密切相关的。可以尝试通过二叉树的数据结构来理解递归是如何将一个问题拆分成若干子问题,求解再回溯的。这里可以参考以下快速排序(QuickSort)的过程(快速排序的核心思想是分治,分治即分而治之,通过递归将原问题分解为若干容易求解的子问题,再通过递归将这些子问题联系起来并向二叉树的上层回溯,最终求解出原问题)。
5.递归的条件
(1)递归的结束条件(不写会死循环,TLE)。
(2)递归最后一层和其他有关系的层的关系怎样用非递归函数来表达。
比如:斐波纳契亚数列,(1)当n=1和n=2的时候f(n)=1,这就是递归的终止条件。给了终止条件,计算机才能进行求解子问题并回溯,最终求出f(n)。
6.汉诺塔问题:
汉诺塔问题是一个经典的问题。汉诺塔(Hanoi Tower),又称河内塔,源于印度一个古老传说。梵天创造世界的时候做了三根金刚石柱子,在一根柱子上从下往上按照大小顺序摞着64片黄金圆盘。大梵天命令婆罗门把圆盘从下面开始按大小顺序重新摆放在另一根柱子上。并且规定,任何时候,在小圆盘上都不能放大圆盘,且在三根柱子之间一次只能移动一个圆盘。问应该如何操作?
递推式:h(x)=2h(x-1)+1
(64)=18446744073709551615
void move(int id, char from, char to) // 打印移动方式:编号,从哪个盘子移动到哪个盘子
{
printf ("step %d: move %d from %c->%c\n", ++cnt, id, from, to);
}
void hanoi(int n, char x, char y, char z)
{
if (n == 0)
return;
hanoi(n - 1, x, z, y);
move(n, x, z);
hanoi(n - 1, y, x, z);
}
如果一个人一次只能上一阶台阶或两上一阶台阶,这段楼梯有20阶,有多少种上法?
int frog(int n)
{
if(n == 1)
{
return 1;
}
if(n == 2)
{
return 2;
}
return frog(n-1) + frog(n-2);
}
这是一段将数组作为楼梯,len作为楼梯的长度
int fid(int len,int *array)
{
if (array[0] = 0)
{
return array[0];
}
else if (array[1] = 1)
{
return array[1];
}
else
{
for (int i = 2; i <= len; ++i)
{
array[i] = array[i - 1] + array[i - 2];
}
return array[len];
}
}
//斐波那数列
#include <stdio.h>
int main()
{
int f[31];
int fid(int n)
{
f[0] = 0;
f[1] = 1;
for(int i = 2; i <= n; ++i)
{
f[i] = f[i-1] + f[i-2];
}
return f[n];
}
return 0;
#define call(...) ({ *(++top) = (Frame) { .pc = 0, __VA_ARGS__ }; })
#define ret() ({ top--; })
#define goto(loc) ({ f->pc = (loc) - 1; })
void hanoi(int n, char from, char to, char via) {
Frame stk[64], *top = stk - 1;
call(n, from, to, via);
for (Frame *f; (f = top) >= stk; f->pc++) {
switch (f->pc) {
case 0: if (f->n == 1) { printf("%c -> %c\n", f->from, f->to); goto(4); } break;
case 1: call(f->n - 1, f->from, f->via, f->to); break;
case 2: call( 1, f->from, f->to, f->via); break;
case 3: call(f->n - 1, f->via, f->to, f->from); break;
case 4: ret(); break;
default: assert(0);
}
}
}
C代码运行的细节:
1. 编写源代码:
程序员使用文本编辑器编写C语言源代码,源代码包含了程序的逻辑和功能描述。
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
2. 预处理(Preprocessing):
在编译之前,源代码会经过预处理。预处理器会执行一些指令,如 #include
指令将头文件内容插入源代码、#define
定义宏等。
// 预处理后的代码
#include <stdio.h>
int main() {
printf("Hello, World!\n");
return 0;
}
3. 编译(Compilation):
预处理后的代码被编译器翻译成汇编语言或机器代码,生成目标文件。目标文件包含了程序的二进制表示,但还没有被链接。
; 编译生成的目标文件(汇编语言表示)
section .data
section .text
global main
main:
; 汇编代码表示
mov eax, 4
mov ebx, 1
mov ecx, message
mov edx, 13
int 0x80
mov eax, 1
xor ebx, ebx
int 0x80
section .data
message db 'Hello, World!', 0
4.链接(Linking):
链接器将目标文件与所需的库文件链接在一起,生成可执行文件。这个过程包括解析外部引用、符号解析等。
5. 加载(Loading):
可执行文件被加载到内存中,并由操作系统执行。操作系统负责将程序加载到适当的内存地址,并开始执行程序。
6. 运行时(Runtime):
程序开始执行,按照编写的逻辑执行相应的指令。变量在栈或堆上分配内存,函数调用时调用栈被创建,程序运行过程中的状态变化都发生在运行时。
7. 结束运行:
程序执行完毕,通过 return
语句或 exit()
函数返回退出码,操作系统释放相关资源,程序运行结束。
总体来说,C语言在运行时经历了预处理、编译、链接、加载和运行时等多个阶段。这些阶段的每一个都对程序的执行起到了关键作用。这也是为什么C语言通常被称为一种编译型语言,因为它的代码在运行之前需要经过编译器的处理。
文件
文件的读取:代码的执行都是在内存当中。内存是一个很宽泛的概念(包括处理器寄存器内存、L1~L3三级缓存、以及RAM,即掉电不保存任何文件),我们使用文件的目的是保存不仅是程序本身,还有文件输入和输出的数据。
1.文件指针
缓冲文件系统中,关键的概念是“文件类型指针”,简称“文件指针”。每个被使用的文件都在内存中开辟了一个相应的文件信息区,用来存放文件的相关信息(如文件的名字,文件状态及文件当前的位置等)。这些信息是保存在一个结构体变量中的。该结构体类型是有系统声明的,取名FILE。
指向和保存文件信息的结构体
struct _iobuf {
char *_ptr;
int _cnt;
char *_base;
int _flag;
int _file;
int _charbuf;
int _bufsiz;
char *_tmpfname;
};
typedef struct _iobuf FILE;
FILE* pf;
FILE* pf是一个指向FILE类型数据的指针变量。可以使pf指向某个文件的文件信息区(是一个结构体变量)。通过该文件信息区中的信息就能够访问该文件。也就是说,通过文件指针变量能够找到与它关联的文件。
文件读写
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include <string.h>
int main()
{
FILE* fp = fopen("test.txt", "a+");
if (NULL == fp) {
printf("打开失败\n");
}
char buffer[] = "hello word";
fwrite(buffer, sizeof(char), strlen(buffer), fp);
fclose(fp);
return 0;
}
2.文件操作指令
指令 | 描述 |
---|---|
rt | 只读打开一个文本文件,只读数据 |
wt | 只写或创建一个新的文本文件,只写数据 |
at | 追加打开一个文本文件,并在文件末尾写数据 |
rb | 只读打开一个二进制数据,只读数据 |
wb | 只写或创建一个新的二进制文件,只写数据 |
ab | 追加打开一个二进制文件,并在文件末尾写数据 |
rt+ | 读写一个文本文件,允许读和写 |
wt+ | 读写打开或建立一个文本文件,允许读写 |
at+ | 读写打开一个文本文件,允许读,或在文件末尾写数据 |
rb+ | 读写一个二进制文件,允许读和写 |
wb+ | 读写打开或建立一个二进制文件,允许读写 |
ab+ | 读写打开一个二进制文件,允许读,或在文件末尾写数据 |
(2)凡用 “r” 打开一个文件时,该文件必须已经存在,且只能从该文件读出;
(3)用 “w” 打开的文件只能向该文件写入。若打开的文件不存在,则以指定的文件名建立该文件,若打开的文件已经存在,则将该文件删去,重建一个新文件;
(4)若要向一个已存在的文件追加新的信息,只能用 “a” 方式打开文件。但此时该文件必须是存在的,否则将会出错。
(5)在打开一个文件时,如果出错,fopen
将返回一个空指针值NULL。在程序中可以用这一信息来判别是否完成打开文件的工作,并作相应的处理。因此常用以下程序段打开文件:
(6)把一个文本文件读入内存时,要将ASCII 码转换成二进制码,而把文件以文本方式写入磁盘时,也要把二进制码转换成ASCII 码,因此文本文件的读写要花费较多的转换时间。对二进制文件的读写不存在这种转换。
(7)标准输入文件(键盘),标准输出文件(显示器),标准出错输出(出错信息)是由系统打开的,可直接使用
预处理器
C语言预处理器是C语言编译器的一部分,它负责在实际编译之前对源代码进行预处理。预处理器通过对源代码进行一系列文本替换和宏展开等操作,生成修改后的源代码,然后将其传递给编译器进行编译。下面是C语言预处理器的一些关键功能和指令:
指令 | 描述 |
---|---|
#define | 定义宏 |
#include | 包含一个源代码文件 |
#undef | 取消已定义的宏 |
#ifdef | 如果宏已经定义,则返回真 |
#ifndef | 如果宏没有定义,则返回真 |
#if | 如果给定条件为真,则编译下面代码 |
#else | #if 的替代方案 |
#elif | 如果前面的 #if 给定条件不为真,当前条件为真,则编译下面代码 |
#endif | 结束一个 #if……#else 条件编译块 |
#error | 当遇到标准错误时,输出错误消息 |
#pragma | 使用标准化方法,向编译器发布特殊的命令到编译器中 |
#define message_for(a, b) \
printf(#a " and " #b ": We love you!\n")
#define tokenpaster(n,token) \
printf (#token #n , token##n)
/*
“#”需要把一个宏的参数转换为字符串常量时,则使用字符串常量化运算符(#)
“##”宏定义内的标记粘贴运算符(##)会合并两个参数。它允许在宏定义中两个独立的标记被合并为一个标记
*/
#define MESSAGE 0
#if !defined (MESSAGE)
#define MESSAGE "You wish!"
#endif
/*
预处理器 defined 运算符是用在常量表达式中的,用来确定一个标识符是否已经使用 #define 定过。如果指定的标识符已定义,则值为真(非零)。如果指定的标识符未定义,则值为假(零)。下面的实例演示了 defined() 运算符的用法:
*/
#define swep(x) ((x)*(x))//外层括号可有可无
/*
在使用带有参数的宏之前,必须使用 #define 指令定义。参数列表是括在圆括号内,且必须紧跟在宏名称的后边。宏名称和左圆括号之间不允许有空格
*/
1.宏替换:
预处理器允许你使用#define
指令定义宏,然后在代码中使用宏名称来代替一组代码或常量。宏替换是一种文本替换,不进行类型检查。例如:
#define PI 3.14159265359
int radius = 5;
double circumference = 2 * PI * radius;
2.条件编译:
预处理器提供条件编译指令,如#if
、#ifdef
、#ifndef
、#elif
、#else
和#endif
,允许根据条件选择性地包含或排除代码块。这对于创建跨平台代码或调试代码非常有用。
3.文件包含:
使用#include
指令,可以将其他源文件包含到当前源文件中。这对于将代码模块化并重复使用代码很有帮助。
4.条件编译宏:
通过定义条件编译宏,可以根据编译器选项或平台选择性地编译不同部分的代码。例如:
#ifdef DEBUG
// 调试代码
#endif
4.字符串化:
使用#
操作符,你可以将宏参数转换为字符串常量。这对于生成调试信息或日志消息时很有用。
#define LOG(message) printf("Log: %s\n", #message)
5.宏连接:
使用##
操作符,你可以将两个宏参数连接在一起。这对于创建动态变量名或函数名很有帮助。
#define CONCAT(a, b) a##b
int xy = 42;
6.去掉注释:
预处理器会删除源代码中的注释,以减小编译后的文件大小。
7.预定义宏:
预处理器定义了一些特定的预定义宏,例如__FILE__
、__LINE__
、__DATE__
和__TIME__
,可以在代码中使用以获取文件名、行号和编译时间等信息。
8.头文件保护:
使用头文件保护宏,可以防止头文件的多次包含。这是通过在头文件中使用条件编译指令来实现的。
C语言预处理器在编译过程中起到重要作用,它能够在编译前修改源代码,使得代码更加灵活和可维护。然而,要谨慎使用宏,以避免潜在的代码可读性和错误调试问题。
define关键字的使用
#define
是C和C++等编程语言中的一个预处理指令,用于创建宏定义(宏),可以用来定义常量、函数、条件编译以及进行代码替换等。以下是#define
的使用方法:
1.定义常量:
#define PI 3.14159265359
这会创建一个名为 PI
的常量,其值为 3.14159265359
。这种常量通常用于提高代码的可读性,避免在多个地方使用硬编码的值。
2.定义函数宏:
#define SQUARE(x) ((x) * (x))
这将创建一个函数宏 SQUARE
,它接受一个参数 x
并返回 x * x
。函数宏通常用于简化代码,减少重复。
3.条件编译:
#define DEBUG
#ifdef DEBUG
// 在调试模式下执行的代码
#endif
在这个示例中,#define DEBUG
用于定义一个宏,然后使用 #ifdef
来检查是否定义了 DEBUG
宏。根据条件编译,可以选择性地包含或排除特定的代码块。
4.条件编译中的宏定义:
#define MAX_SIZE 100
#ifdef MAX_SIZE
int arr[MAX_SIZE];
#endif
这里,MAX_SIZE
宏在条件编译中用于确定数组的最大大小。
5.使用宏定义进行代码替换:
#define MAX(a, b) ((a > b) ? a : b)
int result = MAX(x, y);
在这个示例中,MAX
宏定义用于比较两个值 x
和 y
并返回较大的值。这在简化代码和提高可读性方面非常有用。
6.字符串连接:
#define FULL_NAME(first, last) (first " " last)
char name[] = FULL_NAME("John", "Doe");
FULL_NAME
宏定义用于将两个字符串连接成一个完整的名字。
7.条件宏定义:
#define MAX_SIZE 100
#ifndef MAX_SIZE
#define MAX_SIZE 50
#endif
这个示例中,MAX_SIZE
宏在没有定义的情况下被设置为默认值。
需要注意的是,宏定义会进行简单的文本替换,因此在使用时要小心,确保不会引发不必要的副作用或错误。此外,#define
中的括号是为了确保在宏展开时表达式的优先级正确,特别是在复杂的宏中。维护和调试宏定义时需要小心,以避免潜在的问题。
预定义宏
C语言中有一些预定义的宏,它们是由编译器提供的,并可在程序中直接使用。这些宏通常用于获取关于编译环境和程序自身的信息。以下是一些常见的C语言预定义宏:
1. __FILE__
:
表示当前源文件的文件名。
#include <stdio.h>
int main() {
printf("Current file: %s\n", __FILE__);
return 0;
}
2. __LINE__
:
表示当前源文件中的行号。
#include <stdio.h>
int main() {
printf("Current line: %d\n", __LINE__);
return 0;
}
3. __DATE__
:
表示程序被编译的日期,以字符串形式表示。
#include <stdio.h>
int main() {
printf("Compilation date: %s\n", __DATE__);
return 0;
}
4. __TIME__
:
表示程序被编译的时间,以字符串形式表示。
#include <stdio.h>
int main() {
printf("Compilation time: %s\n", __TIME__);
return 0;
}
5. __func__
:
表示当前函数的名称。
#include <stdio.h>
void exampleFunction() {
printf("Current function: %s\n", __func__);
}
int main() {
exampleFunction();
return 0;
}
6. __STDC__
:
用于判断是否符合ISO C标准。
#include <stdio.h>
int main() {
#ifdef __STDC__
printf("Conforms to ISO C standard\n");
#else
printf("Does not conform to ISO C standard\n");
#endif
return 0;
}
7. __cplusplus
:
用于判断是否为C++环境。
#include <stdio.h>
int main() {
#ifdef __cplusplus
printf("Compiled as C++\n");
#else
printf("Compiled as C\n");
#endif
return 0;
}
8. __PRETTY_FUNCTION__
(GCC 特有):
GCC编译器提供的宏,表示当前函数的可读形式。
#include <stdio.h>
void exampleFunction() {
printf("Current function: %s\n", __PRETTY_FUNCTION__);
}
int main() {
exampleFunction();
return 0;
}
这些预定义宏为程序员提供了一些有用的信息,可以在程序中用于调试、记录信息或根据不同的编译环境执行不同的操作。在实际编程中,了解和善用这些宏可以提高代码的可维护性和可移植性。
错误输出
在C语言中,你可以使用标准错误流(stderr
)来输出错误消息和诊断信息,这些错误消息通常用于指示程序在运行时遇到了问题。C语言提供了一些用于向标准错误流输出信息的函数和宏。以下是在C语言中进行错误输出的一些常见方法:
错误函数 | 描述 |
---|---|
strerror() | strerror() 函数,返回一个指针,指针指向当前 errno 值的文本表示形式。 |
errno | printf(”%m”, errno); |
perror() | **perror() 函数显示您传给它的字符串,后跟一个冒号、一个空格和当前 errno 值的文本表示形式 |
1.使用fprintf
函数:
fprintf
函数可以将输出发送到指定的流,包括标准错误流stderr
。你可以使用它来输出错误消息。
#include <stdio.h>
int main() {
fprintf(stderr, "这是一个错误消息。\n");
return 1; // 返回非零值表示程序出错
}
在上面的示例中,fprintf
函数将错误消息发送到stderr
流,并返回非零值以表示程序出错。
2.使用perror
函数:
perror
函2数用于将一个描述性错误消息与当前错误代码一起输出到标准错误流。通常,它用于显示与系统调用相关的错误消息。
#include <stdio.h>
#include <errno.h>
int main() {
FILE *file = fopen("non_existent_file.txt", "r");
if (file == NULL) {
perror("打开文件时发生错误");
return 1;
}
return 0;
}
在上面的示例中,如果文件打开失败,perror
函数将显示一个描述性错误消息,并指示错误类型。
3.使用assert
宏:
assert
宏用于在程序中添加断言,以检查条件是否为真。如果断言条件为假,assert
会将错误消息发送到标准错误流并终止程序。
#include <stdio.h>
#include <assert.h>
int main() {
int x = 10;
assert(x == 20); // 断言条件为假,程序终止,错误消息发送到stderr
return 0;
}
注意:在发布版本中,assert
通常被禁用,因此它在生产环境中不会触发。
4.使用exit
函数:
你可以使用exit
函数来立即终止程序的执行,并指定一个退出码。通常,非零的退出码用于指示程序出错。
#include <stdio.h>
int main() {
// 程序逻辑...
if (/*某个条件不满足*/) {
fprintf(stderr, "发生错误。\n");
return 1; // 返回非零值表示程序出错
}
return 0;
}
在上面的示例中,如果某个条件不满足,程序将输出错误消息并返回非零值以指示错误。
无论你使用哪种方法,都可以在C语言程序中进行错误输出,以便诊断和处理运行时错误。通常,标准错误流stderr
用于输出错误消息,而标准输出流stdout
用于正常输出。
C可变参数
在C语言中,你可以使用可变参数函数(Variadic Functions)来编写能够接受不定数量参数的函数。C语言的标准库中包含了一些使用可变参数的函数,例如printf
和scanf
。要创建自己的可变参数函数,你需要使用标准库头文件stdarg.h
中的一些宏和函数。以下是关于C可变参数的详细介绍:
1.使用stdarg.h
头文件:
要使用可变参数函数,首先需要包含stdarg.h
头文件,该头文件包含了处理可变参数的宏和函数。
#include <stdarg.h>
2.创建可变参数函数:
要创建一个可变参数函数,你需要按照以下步骤进行:
-
定义一个带有固定参数的函数,然后在参数列表的末尾添加省略号(
...
),表示接受可变参数。int sum(int count, ...) { // 可变参数函数体 }
-
在函数内部,你可以使用宏
va_list
、va_start
和va_arg
来访问可变参数列表。va_list
:声明一个可变参数列表。va_start
:初始化可变参数列表,使其指向第一个可变参数。va_arg
:从可变参数列表中获取下一个参数的值,根据参数的类型和位置。
-
在函数结束时,使用
va_end
来清理可变参数列表。va_end(arg_list);
3.示例:计算可变参数的和
以下是一个简单的例子,演示如何创建一个可变参数函数来计算传递给它的整数参数的和:
#include <stdio.h>
#include <stdarg.h>
int sum(int count, ...) {
va_list arg_list;
va_start(arg_list, count);
int result = 0;
for (int i = 0; i < count; i++) {
int num = va_arg(arg_list, int);
result += num;
}
va_end(arg_list);
return result;
}
int main() {
int total = sum(5, 1, 2, 3, 4, 5);
printf("总和:%d\n", total);
return 0;
}
在这个示例中,sum
函数接受一个整数参数count
,表示后续将传递的整数参数数量不定。它使用va_list
、va_start
和va_arg
来访问可变参数列表,并计算它们的总和。
使用可变参数函数时,请确保提供足够的信息来正确处理参数。可变参数函数通常不提供参数类型检查,因此在使用时要小心,确保传递的参数数量和类型与函数的期望相匹配。
C内存管理
C语言的内存管理是程序员手动分配和释放内存的过程,它是一项重要的任务,用于确保程序正确、高效地使用计算机内存资源。C语言提供了一些标准库函数来进行内存分配和释放,主要包括malloc
、calloc
、realloc
和free
等函数。以下是关于C语言内存管理的详细介绍:
函数原型 | 描述 |
---|---|
void *calloc(int num, int size); | 在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是 0。 |
void free(void *address); | 该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。 |
void *malloc(int num); | 在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。 |
void *realloc(void *address, int newsize); | 该函数重新分配内存,把内存扩展到 newsize。 |
1.内存分配函数:
-
malloc
(Memory Allocation):malloc
函数用于动态分配指定大小的内存块,并返回一个指向首地址的指针。分配的内存不会被初始化,可能包含随机数据。void *malloc(size_t size);
示例:
int *arr = (int *)malloc(sizeof(int) * 10);
-
calloc
(Contiguous Allocation):calloc
函数用于动态分配指定数量和大小的连续内存块,并返回一个指向首地址的指针。分配的内存会被初始化为零。void *calloc(size_t num_elements, size_t element_size);
示例:
int *arr = (int *)calloc(10, sizeof(int));
2.内存释放函数:
-
free
:free
函数用于释放通过malloc
、calloc
或realloc
分配的内存块,以便将其返回给系统以供重新分配。void free(void *ptr);
示例:
free(arr);
3.内存重新分配函数:
-
realloc
(Reallocate):realloc
函数用于更改先前分配的内存块的大小。它接受一个指向已分配内存的指针,以及新的大小,并返回一个指向重新分配内存的指针。如果无法在原地重新分配,则会分配新的内存块,并将数据从旧内存复制到新内存。void *realloc(void *ptr, size_t new_size);
示例:
int *new_arr = (int *)realloc(arr, sizeof(int) * 20);
4.内存管理的注意事项:
-
内存泄漏:不释放已分配的内存会导致内存泄漏。每次使用
malloc
、calloc
或realloc
分配内存后,都应该使用free
来释放内存。 -
野指针:访问已释放的内存或未初始化的指针会导致未定义的行为。确保在释放内存后将指针置为
NULL
,以避免野指针。 -
越界访问:不要访问数组或内存块的越界部分,这可能会破坏数据或导致程序崩溃。
-
内存分配失败:内存分配函数可能因为内存不足而失败,因此应该检查它们的返回值,确保分配成功。
-
合理使用动态内存:动态内存分配应该谨慎使用,避免不必要的内存分配和释放操作,以提高程序性能和稳定性。
C语言的内存管理是程序员的责任,需要小心谨慎地处理,以避免内存泄漏、野指针和其他内存相关的问题。最好的实践是在分配内存后及时释放它,并确保正确处理所有边界情况。如果不小心处理内存,可能会导致严重的程序错误。
System()函数详解
在C语言中,system
函数是一个标准库函数,用于执行命令。system
函数的原型如下:
int system(const char *command);
它接受一个字符串参数 command
,该字符串包含要在命令行中执行的命令。system
函数返回一个整数值,该值通常用于表示命令的执行结果。返回值的具体含义取决于系统和命令的执行情况。
1.使用示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
int result;
// 在命令行执行 "ls" 命令(列出当前目录内容)
result = system("ls");
if (result == 0) {
printf("Command executed successfully.\n");
} else {
printf("Command execution failed.\n");
}
return 0;
}
2.注意事项:
-
返回值:
system
函数的返回值可以用于判断命令执行的成功与否。一般而言,如果命令成功执行,返回值为 0;否则,返回一个表示错误的非零值。 -
阻塞特性:
system
函数会阻塞调用进程的执行,直到执行的命令完成。在命令执行完成之前,system
函数不会返回。 -
命令字符串: 命令字符串可以包含任何合法的系统命令。例如,在Windows系统上,你可以执行
system("dir")
,在Unix/Linux系统上,你可以执行system("ls")
。 -
安全性: 注意,使用
system
函数存在一些安全性问题,尤其是当命令字符串包含用户输入时,可能会受到命令注入攻击。在这种情况下,最好使用更安全的执行命令的方式,比如exec
系列函数。
总体而言,system
函数提供了一个简单的方法来在C程序中执行命令,但在一些安全性要求较高的情境下,可能需要考虑使用更为安全的方式来执行外部命令。
3.命令一揽表
指令 | 描述 |
---|---|
color 0A | 其中color后面的0是背景色代号,A是前景色代号。各颜色代码如下: 0=黑色 1=蓝色 2=绿色 3=湖蓝色 4=红色 5=紫色 6=黄色 7=白色 8=灰色 9=淡蓝色 A=淡绿色 B=淡浅绿色 C=淡红色 D=淡紫色 E=淡黄色 F=亮白色 |
ASSOC | 显示或修改文件扩展名关联 |
AT | 计划在计算机上运行的命令和程序 |
ATTRIB | 显示或更改文件属性 |
BREAK | 设置或清除扩展式 CTRL+C 检查 |
CACLS | 显示或修改文件的访问控制列表(ACLs) |
CALL | 从另一个批处理程序调用这一个 |
CD | 显示当前目录的名称或将其更改 |
CHCP | 显示或设置活动代码页数 |
CHDIR | 显示当前目录的名称或将其更改 |
CHKDSK | 检查磁盘并显示状态报告 |
CHKNTFS | 显示或修改启动时间磁盘检查 |
CLS | 清除屏幕 |
CMD | 打开另一个 Windows 命令解释程序窗口 |
COLOR | 设置默认控制台前景和背景颜色 |
COMP | 比较两个或两套文件的内容 |
COMPACT | 显示或更改 NTFS 分区上文件的压缩 |
CONVERT | 将 FAT 卷转换成 NTFS。您不能转换当前驱动器 |
COPY | 将至少一个文件复制到另一个位置 |
DATE | 显示或设置日期 |
DEL | 删除至少一个文件 |
DIR | 显示一个目录中的文件和子目录 |
DISKCOMP | 比较两个软盘的内容 |
DISKCOPY | 将一个软盘的内容复制到另一个软盘 |
DOSKEY | 编辑命令行、调用 Windows 命令并创建宏 |
ECHO | 显示消息,或将命令回显打开或关上 |
ENDLOCAL | 结束批文件中环境更改的本地化 |
ERASE | 删除至少一个文件 |
EXIT | 退出 CMD.EXE 程序(命令解释程序) |
FC | 比较两个或两套文件,并显示不同处 |
FIND | 在文件中搜索文字字符串 |
FINDSTR | 在文件中搜索字符串 |
FOR | 为一套文件中的每个文件运行一个指定的命令 |
FORMAT | 格式化磁盘,以便跟 Windows 使用 |
FTYPE | 显示或修改用于文件扩展名关联的文件类型 |
GOTO | 将 Windows 命令解释程序指向批处理程序中某个标明的行 |
GRAFTABL | 启用 Windows 来以图像模式显示扩展字符集 |
HELP | 提供 Windows 命令的帮助信息 |
IF | 执行批处理程序中的条件性处理 |
LABEL | 创建、更改或删除磁盘的卷标 |
MD | 创建目录 |
MKDIR | 创建目录 |
MODE | 配置系统设备。 |
MORE | 一次显示一个结果屏幕。 |
MOVE | 将文件从一个目录移到另一个目录。 |
PATH | 显示或设置可执行文件的搜索路径。 |
PAUSE | 暂停批文件的处理并显示消息。 |
POPD | 还原 PUSHD 保存的当前目录的上一个值。 |
打印文本文件。 | |
PROMPT | 更改 Windows 命令提示符。 |
PUSHD | 保存当前目录,然后对其进行更改 |
RD | 删除目录。 |
RECOVER | 从有问题的磁盘恢复可读信息。 |
REM | 记录批文件或 CONFIG.SYS 中的注释。 |
REN | 重命名文件。 |
RENAME | 重命名文件。 |
REPLACE | 替换文件。 |
RMDIR | 删除目录。 |
SET | 显示、设置或删除 Windows 环境变量。 |
SETLOCAL | 开始批文件中环境更改的本地化。 |
SHIFT | 更换批文件中可替换参数的位置。 |
SORT | 对输入进行分类。 |
START | 启动另一个窗口来运行指定的程序或命令。 |
SUBST | 将路径跟一个驱动器号关联。 |
TIME | 显示或设置系统时间。 |
TITLE | 设置 CMD.EXE 会话的窗口标题。 |
TREE | 以图形模式显示驱动器或路径的目录结构。 |
TYPE | 显示文本文件的内容。 |
VER | 显示 Windows 版本。 |
VERIFY | 告诉 Windows 是否验证文件是否已正确写入磁盘 |
VOL | 显示磁盘卷标和序列号。XCOPY 复制文件和目录树。 |
C枚举
C语言中的枚举(Enumeration)是一种用户定义的数据类型,用于创建一组具有离散值的常量。枚举允许程序员为常用的值分配易于记忆的名称,以提高代码的可读性和可维护性。以下是关于C语言枚举的详细解释:
1.定义一个枚举类型:
在C语言中,你可以使用enum
关键字来定义一个枚举类型。定义枚举类型的一般语法如下:
enum EnumName {
Value1,
Value2,
// ...
ValueN
};
EnumName
是枚举类型的名称,可以自定义。Value1
、Value2
、…、ValueN
是枚举常量的名称。它们被称为枚举成员,它们的值默认从0开始递增。
2.使用枚举类型:
一旦定义了枚举类型,你可以创建该类型的变量,并将其赋值为枚举成员之一。
enum EnumName day = Value2;
这里,我们创建了一个名为day
的变量,并将其赋值为EnumName
枚举类型中的一个成员,如Value2
。
3.枚举成员的整数值:
枚举成员默认从0开始递增,但你也可以显式地为它们分配整数值。例如:
enum Color {
RED = 1,
GREEN = 2,
BLUE = 4,
YELLOW = 5
};
在这个示例中,枚举成员RED
的值为1,GREEN
的值为2,以此类推。
4.访问枚举成员的值:
你可以使用枚举成员的名称来访问它们的值。例如:
enum Color myColor = GREEN;
printf("The color is %d\n", myColor);
这将输出 “The color is 2”,因为myColor
被赋值为GREEN
,而GREEN
的值为2。
5.枚举的用途:
枚举常常用于以下情况:
- 表示一组相关的选项或状态,如颜色、星期几、月份等。
- 增强代码的可读性和可维护性,因为枚举成员通常具有描述性的名称。
- 在条件语句、开关语句等中用作标志。
例如,你可以使用枚举来表示一周的天:
enum Day {
SUNDAY,
MONDAY,
TUESDAY,
WEDNESDAY,
THURSDAY,
FRIDAY,
SATURDAY
};
这样,你可以更容易地理解和操作与日期相关的代码。
C语言中的枚举是一种强大的工具,用于创建一组有序的常量,提高代码的可读性和可维护性。通过为枚举成员分配整数值,你还可以灵活地控制这些常量的值。
C函数指针和回调函数
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
typedef int (*Fun1)(int);//声明也可写成int (*Fun1)(int x),但习惯上一般不这样。
typedef int (*Fun2)(int, int);//参数为两个整型,返回值为整型
typedef void (*Fun3)(void);//无参数和返回值
typedef void* (*Fun4)(void*);//参数和返回值都为void*指针
int max(int x, int y)
{
return x > y ? x : y;
}
int main(void)
{
int (*p)(int, int) = &max; // &可以省略,函数指针可以在函数中定义
int a, b, c, d;
printf("请输入三个数字:");
scanf("%d %d %d", &a, &b, &c);
d = p(p(a, b), c);
printf("最大的数字是: %d\n", d);
return 0;
}
1.指向函数的指针
#include<stdio.h>
int function(int i){
return i*i;
}
int (*func_p)(int);
int main(void){
func_p=function;
printf("%d",func_p(10));
return 0;
}
//指向函数的指针的数组
#include<stdio.h>
void func1(int i){
printf("1");
}
void func2(int i){
printf("2");
}
void func3(int i){
printf("3");
}
void func4(int i){
printf("4");
}
void (*func_table[])(int)={
func1,
func2,
func3,
func4
};
int main(void){
func_table[1](1);
return 0;
}
2.函数指针作为函数的参数
#include <stdio.h>
#include <stdlib.h>
typedef void(*FunType)(int);
//前加一个typedef关键字,这样就定义一个名为FunType函数指针类型,而不是一个FunType变量。
//形式同 typedef int* PINT;
void myFun(int x);
void hisFun(int x);
void herFun(int x);
void callFun(FunType fp,int x);
int main()
{
callFun(myFun,100);//传入函数指针常量,作为回调函数
callFun(hisFun,200);
callFun(herFun,300);
return 0;
}
void callFun(FunType fp,int x)
{
fp(x);//通过fp的指针执行传递进来的函数,注意fp所指的函数有一个参数
}
void myFun(int x)
{
printf("myFun: %d\n",x);
}
void hisFun(int x)
{
printf("hisFun: %d\n",x);
}
void herFun(int x)
{
printf("herFun: %d\n",x);
}
3.函数指针作为函数返回类型
void (* func5(int, int, float ))(int, int)
{
...
}
4.函数指针数组
/* 方法1 */
void (*func_array_1[5])(int, int, float);
/* 方法2 */
typedef void (*p_func_array)(int, int, float);
p_func_array func_array_2[5];
5.回调函数的定义:
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
把一段可执行的代码像参数传递那样传给其他代码,而这段代码会在某个时刻被调用执行,这就叫做回调。
如果代码立即被执行就称为同步回调,如果过后再执行,则称之为异步回调。
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。
回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应
#include <stdio.h>
int Callback_1(int a) ///< 回调函数1
{
printf("Hello, this is Callback_1: a = %d ", a);
return 0;
}
int Callback_2(int b) ///< 回调函数2
{
printf("Hello, this is Callback_2: b = %d ", b);
return 0;
}
int Callback_3(int c) ///< 回调函数3
{
printf("Hello, this is Callback_3: c = %d ", c);
return 0;
}
int Handle(int x, int (*Callback)(int)) ///< 注意这里用到的函数指针定义
{
Callback(x);
}
int main()
{
Handle(4, Callback_1);
Handle(5, Callback_2);
Handle(6, Callback_3);
return 0;
}
位域
C语言中的位域(Bit-fields)是一种数据结构,用于将数据字段划分为位段,以便有效地使用内存空间。位域允许你指定一个字段的宽度(以位为单位),从而节省内存。通常,位域用于存储和操作具有特定位数的标志或状态信息。以下是关于C语言位域的详细介绍:
1.位域的定义:
在C语言中,你可以使用struct
结构体来定义位域。在结构体中,你可以使用冒号(:
)运算符来指定字段的位宽度。例如:
struct Flags {
unsigned int isOn : 1; // 占1位
unsigned int isWorking : 1; // 占1位
unsigned int color : 3; // 占3位
};
在这个示例中,我们定义了一个struct
结构体,包含了三个位域成员。
2.位域的操作:
使用位域后,你可以像操作普通整数一样操作位域成员。例如,设置位域的值,读取位域的值,或者进行位运算。以下是一些位域操作的示例:
struct Flags status;
status.isOn = 1; // 设置isOn位域为1
status.isWorking = 0; // 设置isWorking位域为0
status.color = 2; // 设置color位域为2
int working = status.isWorking; // 读取isWorking位域的值
int color = status.color; // 读取color位域的值
3.注意事项:
-
位宽度小于数据类型大小:位域的宽度不能大于其数据类型的大小。例如,一个
char
类型的位域不能有10位。 -
位域的顺序:位域的存储顺序(高位到低位或低位到高位)取决于编译器和平台,可能会有不同的实现。
-
跨字节边界:位域的定义可能会跨越字节边界,这取决于编译器的实现。这可能会导致存储和访问效率问题。
-
不具有地址:位域没有自己的地址,因此无法对位域进行取址操作(即不能使用
&
运算符)。 -
位域的移植性:由于位域的实现因编译器和平台而异,因此在不同系统上的行为可能会有所不同。在编写具有跨平台需求的代码时,要小心使用位域。
位域在某些情况下非常有用,例如在嵌入式系统中,需要紧凑地表示硬件寄存器的位。然而,由于其实现的不确定性,位域在通用应用中使用较少。如果需要确保特定的位操作行为,通常更安全的方法是使用位掩码和位运算符。
共用体
C语言中的共用体(Union
)是一种特殊的数据结构,允许在相同的内存位置存储不同的数据类型。共用体的成员共享相同的内存空间,但只能存储其中一个成员的值。这使得共用体非常适合用于需要节省内存或处理多种数据类型的情况。以下是有关C语言共用体的详细介绍:
1.共用体的定义:
共用体的定义类似于结构体,但使用关键字union
。例如:
union Data {
int i;
float f;
char str[20];
};
在这个示例中,我们定义了一个名为Data
的共用体,它包含了三个不同的成员:一个整数i
、一个浮点数f
和一个字符数组str
。
2.共用体的大小:
共用体的大小等于其成员中最大的成员的大小。在上面的示例中,如果int
占用4个字节,float
占用4个字节,而字符数组str
占用20个字节,那么整个共用体的大小将是20个字节。
3.共用体的使用:
可以使用共用体成员的方式与访问结构体的成员相同。然而,由于共用体的成员共享内存,只能存储其中一个成员的值。例如:
union Data data;
data.i = 10; // 存储整数值
data.f = 3.14; // 存储浮点数值
strcpy(data.str, "Hello"); // 存储字符串
printf("整数值:%d\n", data.i); // 输出整数值
printf("浮点数值:%f\n", data.f); // 输出浮点数值
printf("字符串:%s\n", data.str); // 输出字符串
这里的关键点是,在每次赋值给共用体时,之前存储的值将被覆盖。
4.共用体的应用:
共用体通常用于以下情况:
-
节省内存:当你需要多种数据类型共享相同的内存位置时,可以使用共用体来节省内存。这对于嵌入式系统和低内存环境非常有用。
-
处理不同数据类型:有时需要处理不同数据类型的数据,例如在解析二进制数据时。共用体可以用于处理这种情况,每次只使用其中一个成员。
-
实现联合体:共用体可用于创建联合体数据结构,其中多个数据成员共享相同的内存位置。
尽管共用体在某些情况下非常有用,但也需要小心使用,因为共用体的成员共享内存,可能导致数据不一致或错误。在使用共用体时,必须确保正确地跟踪哪个成员包含有效的数据。
C结构体
1.结构体的定义
struct tag {
member-list
member-list
member-list
...
} variable-list ;
-> 运算符:用于指针访问结构体成员,语法为 pointer->member,等价于 (*pointer).member。
&是运营商的地址,可以简单地读作“地址”
*是取消引用运算符,可以读作“指向的值”
2.结构体作为参数
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 函数声明 */
void printBook( struct Books *book );
3.指向结构体的指针
在C语言中,你可以使用指针来引用和操作结构体(struct)类型的数据。指向结构体的指针允许你直接访问结构体的成员,而不必复制整个结构体的内容。以下是如何声明、创建和使用指向结构体的指针的基本方法:
1. 定义结构体类型:
首先,你需要定义一个结构体类型。例如,下面定义了一个名为Person
的结构体,包含姓名和年龄:
struct Person {
char name[50];
int age;
};
2. 声明指向结构体的指针:
要声明指向结构体的指针,你需要使用结构体的类型名称,然后在变量名前加上星号*
,如下所示:
struct Person *personPtr;
这将声明一个名为personPtr
的指针,它可以指向Person
类型的结构体。
3. 分配内存并初始化指针:
在使用指针之前,通常需要分配内存来存储结构体的实际数据。可以使用动态内存分配函数malloc
来分配内存,并使用指针来引用它:
personPtr = (struct Person *)malloc(sizeof(struct Person));
这将分配足够的内存以存储一个Person
结构体,并将指针personPtr
指向该内存。
4. 访问结构体成员:
一旦指针指向了结构体,你可以使用箭头运算符->
来访问结构体的成员,如下所示:
strcpy(personPtr->name, "John");
personPtr->age = 30;
这将在结构体内存中设置name
和age
成员的值。
5. 释放内存:
在不再需要结构体时,你应该释放之前分配的内存,以防止内存泄漏:
free(personPtr);
这将释放由malloc
分配的内存。
下面是一个完整的示例,演示了如何声明、创建和使用指向结构体的指针:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person {
char name[50];
int age;
};
int main() {
struct Person *personPtr;
// 分配内存
personPtr = (struct Person *)malloc(sizeof(struct Person));
// 设置结构体成员的值
strcpy(personPtr->name, "John");
personPtr->age = 30;
// 访问并打印结构体成员的值
printf("Name: %s\n", personPtr->name);
printf("Age: %d\n", personPtr->age);
// 释放内存
free(personPtr);
return 0;
}
这个示例演示了如何创建、访问和释放指向结构体的指针。指向结构体的指针非常有用,因为它允许你在不复制结构体的情况下操作结构体数据。
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>
typedef struct List {
int age;
char name[50];
char phone[12];
}Node;
int main() {
Node data1;
Node* pointer = &data1;
data1.age = 19;
*pointer->name = 'yang';
*pointer->phone = "12332";
printf("%d",pointer->age);
printf("%s", pointer->name);
return 0;
}
. 与->在c语言中的区别
在C语言中,.
(点运算符)和->
(箭头运算符)用于访问结构体(或联合体)类型的成员。它们之间的主要区别是用于访问成员的方式和左操作数的类型。
1..
(点运算符):
.
用于访问结构体或联合体类型的成员。- 左操作数必须是一个结构体或联合体的实例,而不是指针。
- 使用
.
时,你直接操作结构体或联合体的实例,而不需要间接引用。
示例:
struct Point {
int x;
int y;
};
struct Point p1;
p1.x = 10;
p1.y = 20;
2.->
(箭头运算符):
->
用于访问结构体或联合体类型的成员,但左操作数是一个指向结构体或联合体的指针。- 当你有一个指向结构体的指针时,你可以使用箭头运算符来访问结构体的成员。
- 箭头运算符允许你通过指针间接引用结构体或联合体的成员。
示例:
struct Point {
int x;
int y;
};
struct Point *ptr = malloc(sizeof(struct Point));
ptr->x = 10;
ptr->y = 20;
3.总结:
- 使用
.
运算符直接访问结构体或联合体的成员,左操作数必须是一个结构体或联合体的实例。 - 使用
->
运算符通过指针访问结构体或联合体的成员,左操作数是指向结构体或联合体的指针。
这些运算符允许你方便地访问和操作结构体类型的数据,无论是直接还是通过指针。
左值和右值的区别
在C语言中,左值(Lvalue
)和右值(Rvalue
)是两个重要的概念,它们用于描述表达式中的值和对象。理解左值和右值的区别对于C语言中的变量、赋值和表达式的行为非常重要。以下是关于左值和右值的详细介绍:
1.左值(Lvalue
):
- 左值是一个标识符或表达式,它表示一个具体的内存位置,可以被赋值。
- 左值通常是变量的名称,也可以是数组元素或结构体成员等。
- 通常,左值是可以出现在赋值操作符(=)的左侧的部分。例如,
x
是一个左值,可以执行x = 10;
这样的赋值操作。 - 左值可以出现在等号的左侧或右侧,但右侧的情况通常用于读取变量的值。
2.右值(Rvalue
):
- 右值是一个表达式,它表示一个具体的值,但通常不能被赋值。
- 右值可以是常量、字面值、函数返回值等。
- 右值通常出现在赋值操作符(=)的右侧,用于提供要赋给左值的值。例如,
x = 10;
中的10
是一个右值。 - 右值可以被用来计算和生成新的右值。例如,
x = 5 + 3;
中的5 + 3
是一个右值表达式,它计算出一个右值8
并将其赋给左值x
。
3.区别和用途:
- 左值表示一个可修改的内存位置,因此可以用于赋值操作。
- 右值表示一个值,通常不能被直接修改,只能用于计算和生成新的右值。
- 在C语言中,函数的返回值通常是右值,而变量的名称通常是左值。
- 左值和右值的概念对于理解C语言中的运算符行为非常重要。例如,递增运算符
++
可以用于左值,但不能用于右值。
示例:
int x = 10; // x 是左值,10 是右值
x = x + 5; // x 是左值,x + 5 是右值
int y = x; // x 是左值,y 是左值,x 的值作为右值
总之,左值表示一个可修改的内存位置,而右值表示一个值。了解左值和右值的区别有助于理解C语言中的变量和表达式的行为,以及如何正确使用它们。左值通常可以出现在赋值操作符的左侧,而右值通常出现在赋值操作符的右侧。
布尔运算
布尔运算是一种逻辑代数,起源于数学家和逻辑学家George Boole的工作。这些运算主要涉及两个值:真(true)和假(false)。布尔运算在计算机科学、电子工程、逻辑设计等领域中得到广泛应用。以下是一些基本的布尔运算和相关概念:
1. 基本布尔运算符:
-
与运算(AND):
- 符号:
&&
或∧
- 表达式: A && B
- 结果: 当且仅当 A 和 B 同时为真时,结果为真。
- 符号:
-
或运算(OR):
- 符号:
||
或∨
- 表达式: A || B
- 结果: 当 A 或 B 任意一个为真时,结果为真。
- 符号:
-
非运算(NOT):
- 符号:
!
或¬
- 表达式: !A
- 结果: 如果 A 为真,则结果为假;如果 A 为假,则结果为真。
- 符号:
2. 布尔代数定律:
-
结合律:
- (A && B) && C = A && (B && C)
- (A || B) || C = A || (B || C)
-
分配律:
- A && (B || C) = (A && B) || (A && C)
- A || (B && C) = (A || B) && (A || C)
-
恒等律:
- A && true = A
- A || false = A
-
零律:
- A && false = false
- A || true = true
3. 布尔表达式:
- 布尔表达式是由变量、常量和布尔运算符构成的表达式。
- 例如:(A && B) || (!C)
4. 真值表:
-
真值表列出了布尔表达式对应的所有输入值的可能组合及其输出。
| A | B | C | A && B | !C | (A && B) || !C | |---|---|---|--------|----|----------------| | T | T | T | T | F | T | | T | T | F | T | T | T | | T | F | T | F | F | F | | T | F | F | F | T | T | | F | T | T | F | F | F | | F | T | F | F | T | T | | F | F | T | F | F | F | | F | F | F | F | T | T |
5. 应用场景:
-
逻辑电路设计:
- 布尔运算用于描述和设计逻辑电路,例如门电路(与门、或门、非门)。
-
计算机程序设计:
- 控制流语句、条件语句和逻辑运算都使用布尔运算。
-
数据库查询:
- 查询语句中经常使用布尔运算符来筛选数据。
-
网络和通信:
- 布尔运算用于定义和控制网络和通信协议。
6.总结:
布尔运算是计算机科学和逻辑设计中的基础,它提供了一种描述和操作逻辑关系的方式。这些运算符和概念在编程、电子工程、数据库管理等领域中都是至关重要的。深入理解布尔运算对于有效地解决逻辑问题和设计复杂系统至关重要。
原码和反码及补码的存在意义
1.存在意义
原码代表着数值的原始二进制形态
反码的作用是为了表示整数(大多数时候为负整数)而存在的
补码可以正整数和负整数进行统一的表示,从而达到方便计算的效果
2.计算方法
eg: 正数和无符号数的原码、反码、补码都是相同的
数值(-5)
原码:1000 0101
反码:1111 1010
补码:1111 1011
反码和补码的计算都排除了符号位
反码的计算方法是除却符号位之外按位取反
补码的计算方法是除却符号位之外在最后一位+1
时间复杂度
时间复杂度是算法运行时间与输入规模之间的关系。它是一种衡量算法效率的方法,通常用大 O 符号(O)表示。时间复杂度描述的是算法的运行时间随着输入规模的增加而增加的趋势。
表示方法:
时间复杂度通常使用大 O 符号来表示,记作 O(f(n)),其中 f(n) 是输入规模 n 的函数。常见的时间复杂度包括 O(1)、O(log n)、O(n)、O(n log n)、O(n^2) 等。
常见时间复杂度:
-
O(1) - 常数时间复杂度: 算法的运行时间是一个常数,不随输入规模变化而变化。例如,直接访问数组中的某个元素。
-
O(log n) - 对数时间复杂度: 算法的运行时间与输入规模的对数呈对数关系。例如,二分查找。
-
O(n) - 线性时间复杂度: 算法的运行时间与输入规模成线性关系。例如,遍历一个数组。
-
O(n log n) - 线性对数时间复杂度: 典型的例子是快速排序、归并排序等分治算法。
-
O(n^2) - 平方时间复杂度: 算法的运行时间与输入规模的平方成正比。例如,简单的嵌套循环。
-
O(2^n) - 指数时间复杂度: 算法的运行时间与输入规模的指数关系。通常是一些递归算法的时间复杂度。
选择时间复杂度的原则:
-
最坏情况下的时间复杂度: 描述算法在最坏情况下的性能。通常使用最坏情况来分析算法,因为它提供了一种保证。
-
平均情况下的时间复杂度: 描述算法在平均情况下的性能。有时候最坏情况并不代表真实情况,因此平均情况分析更为准确。
-
最好情况下的时间复杂度: 描述算法在最好情况下的性能。这很少被使用,因为最好情况通常对应于特殊输入。
例子:
考虑一个简单的算法,计算数组中所有元素的和:
int sum(int array[], int n) {
int result = 0;
for (int i = 0; i < n; i++) {
result += array[i];
}
return result;
}
这个算法的时间复杂度是 O(n),因为运行时间与数组的大小 n 成线性关系。
通过分析时间复杂度,我们可以更好地理解和比较不同算法在处理大规模数据时的性能表现。通常来说,我们希望选择时间复杂度较低的算法,特别是在处理大规模数据时。
算法的意义及其简单应用
1.算法的意义
-
问题解决: 算法是一种问题解决的工具。通过设计和实施算法,我们能够找到有效的方法来解决各种复杂的问题,从而简化和优化任务的执行。
-
自动化: 算法是自动化的基础。它们使计算机能够按照指定的步骤自动执行任务,无需人工干预。这种自动化提高了效率并减少了错误的发生。
-
效率提升: 通过选择合适的算法,可以显著提高任务的执行效率。不同的算法可能在相同的问题上表现不同的效率,因此选择合适的算法对于处理大规模数据或实时任务至关重要。
-
资源优化: 算法设计的目标之一是通过更少的资源(如时间和空间)实现任务。有效的算法可以帮助节省计算机资源,提高系统性能。
-
复杂系统的管理: 在复杂系统中,算法用于协调和管理各个组件的行为。例如,在操作系统中,调度算法用于决定哪个任务在何时执行。
-
决策支持: 在人工智能和数据科学领域,算法用于从大量数据中提取信息、进行预测和做出决策。这对于制定战略、优化业务流程等方面都具有重要意义。
-
加密和安全: 在信息安全领域,算法用于加密和解密数据,以确保通信的机密性和完整性。密码学算法在保护敏感信息方面发挥关键作用。
-
科学研究: 算法在科学研究中用于模拟和分析复杂的现象。例如,数值模拟算法在物理学、天文学等领域中被广泛使用。
这里引用一句名言“程序=算法+数据结构”来描述算法和数据结构的重要性,总而言之,算法是计算机科学和工程中的基石,它们使得我们能够以系统、有效、自动化的方式解决各种问题,并在不同领域取得创新和进步。算法的设计和优化是计算机科学研究中的核心问题之一。
2.简单应用
1. 求多项式 1 − 1 2 + 1 3 − 1 4 + . . . + 1 99 − 1 100 的值,用 C 语言表述 1.求多项式1-\frac{1}{2}+\frac{1}{3}-\frac{1}{4}+...+\frac{1}{99}-\frac{1}{100}的值,用C语言表述 1.求多项式1−21+31−41+...+991−1001的值,用C语言表述
#include<stdio.h>
int main(){
int main() {
int sigh = 1;
double deno = 2.0, sum = 1.0, term;
while (deno <= 100) {
sigh = -sigh;
term = sigh / deno;
sum = sum + term;
deno = deno + 1;
}
printf("%f",sum);
return 0;
}
OUTPUT:0.688172
2. 给出三角形的三边长,求出三角形的面积,已知三角形面积公式为: a r e a = s ( s − a ) ( s − b ) ( s − c ) . 其中 , s = ( a + b + c ) / 2. 2.给出三角形的三边长,求出三角形的面积,已知三角形面积公式为:area=\sqrt{s(s-a)(s-b)(s-c)}.其中, \\s=(a+b+c)/2. 2.给出三角形的三边长,求出三角形的面积,已知三角形面积公式为:area=s(s−a)(s−b)(s−c).其中,s=(a+b+c)/2.
#include<stdio.h>
#include<math.h>
int main() {
double a=3.67, b=5.42, c=6.21,s, area;
s = (a + b + c) / 2;
area = sqrt(s * (s - a) * (s - b) * (s - c));
printf("a=%.2f,b=%.2f,c=%.2f\n", a, b, c);
printf("area=%f", area);
return 0;
}
OUTPUT:a=3.67,b=5.42,c=6.21area=9.887942
数据结构简单代码
注释: 数据结构篇略微晦涩,所以只提供代码及其简单注释,不会提供定义,结构,方式,特点等文字知识点。并且由于编译器类型导致语法之间的差异,代码执行过程与结果会有所不同,但其中的方法却大致不差。
线性表篇
顺序表
#include <stdio.h>
#include <stdlib.h>
#define MAX_SIZE 100 // 定义顺序表的最大容量
// 定义顺序表结构体
typedef struct {
int data[MAX_SIZE]; // 存储数据的数组
int length; // 当前元素个数
} SeqList;
// 初始化顺序表
void InitList(SeqList *list) {
list->length = 0; // 将顺序表长度初始化为0
}
// 在指定位置插入元素
int Insert(SeqList *list, int position, int value) {
// 判断插入位置是否合法
if (position < 0 || position > list->length || list->length == MAX_SIZE) {
printf("插入位置不合法或顺序表已满\n");
return 0; // 返回0表示插入失败
}
// 将插入位置后的元素依次向后移动一位
for (int i = list->length - 1; i >= position; i--) {
list->data[i + 1] = list->data[i];
}
// 将新元素插入到指定位置
list->data[position] = value;
list->length++; // 长度加1
return 1; // 返回1表示插入成功
}
// 删除指定位置的元素
int Delete(SeqList *list, int position) {
// 判断删除位置是否合法
if (position < 0 || position >= list->length) {
printf("删除位置不合法\n");
return 0; // 返回0表示删除失败
}
// 将删除位置后的元素依次向前移动一位
for (int i = position; i < list->length - 1; i++) {
list->data[i] = list->data[i + 1];
}
list->length--; // 长度减1
return 1; // 返回1表示删除成功
}
// 打印顺序表中的所有元素
void PrintList(SeqList *list) {
printf("顺序表中的元素为:");
for (int i = 0; i < list->length; i++) {
printf("%d ", list->data[i]);
}
printf("\n");
}
int main() {
SeqList list;
InitList(&list); // 初始化顺序表
// 在顺序表中插入元素
Insert(&list, 0, 1);
Insert(&list, 1, 2);
Insert(&list, 2, 3);
Insert(&list, 1, 4);
PrintList(&list); // 打印顺序表中的元素
// 删除顺序表中的元素
Delete(&list, 1);
Delete(&list, 0);
PrintList(&list); // 打印删除后的顺序表中的元素
return 0;
}
单链表
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构
struct Node {
int data; // 节点数据
struct Node* next; // 指向下一个节点的指针
};
// 初始化链表
struct Node* initList() {
return NULL; // 返回空指针表示空链表
}
// 在链表末尾插入新节点
struct Node* insertEnd(struct Node* head, int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
if (!newNode) {
printf("内存分配失败\n");
return head;
}
newNode->data = value;
newNode->next = NULL;
if (!head) {
// 如果链表为空,新节点即为头节点
head = newNode;
} else {
// 否则,在链表末尾插入新节点
struct Node* temp = head;
while (temp->next) {
temp = temp->next;
}
temp->next = newNode;
}
return head;
}
// 在链表头部插入新节点
struct Node* insertFront(struct Node* head, int value) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
if (!newNode) {
printf("内存分配失败\n");
return head;
}
newNode->data = value;
newNode->next = head;
// 新节点成为新的头节点
head = newNode;
return head;
}
// 在链表中删除指定数值的节点
struct Node* deleteNode(struct Node* head, int value) {
struct Node* current = head;
struct Node* prev = NULL;
// 遍历链表,找到待删除节点
while (current && current->data != value) {
prev = current;
current = current->next;
}
if (!current) {
// 未找到要删除的节点
printf("未找到数值为 %d 的节点\n", value);
return head;
}
if (!prev) {
// 要删除的是头节点
head = head->next;
} else {
// 删除中间或末尾节点
prev->next = current->next;
}
free(current); // 释放内存
return head;
}
// 打印链表
void printList(struct Node* head) {
printf("链表: ");
struct Node* current = head;
while (current) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 释放链表内存
void freeList(struct Node* head) {
struct Node* current = head;
while (current) {
struct Node* temp = current;
current = current->next;
free(temp);
}
}
int main() {
struct Node* myList = initList();
myList = insertEnd(myList, 1);
myList = insertEnd(myList, 2);
myList = insertFront(myList, 0);
myList = insertEnd(myList, 3);
myList = insertFront(myList, -1);
printList(myList);
myList = deleteNode(myList, 2);
myList = deleteNode(myList, -1);
printList(myList);
freeList(myList);
return 0;
}
单循环链表
#include <stdio.h>
#include <stdlib.h>
// 定义链表节点结构
struct Node {
int data;
struct Node *next;
};
// 创建新节点
struct Node *createNode(int data) {
struct Node *newNode = (struct Node *)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->data = data;
newNode->next = NULL;
return newNode;
}
// 插入节点到链表末尾
void insertAtEnd(struct Node **head, int data) {
struct Node *newNode = createNode(data);
if (*head == NULL) {
// 如果链表为空,将新节点作为头节点
*head = newNode;
(*head)->next = *head; // 循环链接到自己
} else {
// 否则,找到链表末尾,将新节点插入
struct Node *last = *head;
while (last->next != *head) {
last = last->next;
}
last->next = newNode;
newNode->next = *head; // 循环链接到头节点
}
}
// 打印循环链表
void printList(struct Node *head) {
if (head == NULL) {
printf("链表为空\n");
return;
}
struct Node *current = head;
do {
printf("%d -> ", current->data);
current = current->next;
} while (current != head);
printf("(循环)\n");
}
int main() {
// 初始化空链表
struct Node *head = NULL;
// 插入节点到链表末尾
insertAtEnd(&head, 1);
insertAtEnd(&head, 2);
insertAtEnd(&head, 3);
insertAtEnd(&head, 4);
// 打印链表
printf("循环链表内容:\n");
printList(head);
// 释放链表节点内存(在实际应用中可能需要更详细的释放内存的过程)
struct Node *current = head;
struct Node *next;
do {
next = current->next;
free(current);
current = next;
}
双链表
#include <stdio.h>
#include <stdlib.h>
// 双链表节点结构体
struct Node {
int data;
struct Node* prev; // 指向前一个节点的指针
struct Node* next; // 指向下一个节点的指针
};
// 双链表结构体
struct LinkedList {
struct Node* head; // 指向链表头节点的指针
};
// 初始化双链表
void initializeList(struct LinkedList* list) {
list->head = NULL;
}
// 在链表末尾插入节点
void insertAtEnd(struct LinkedList* list, int data) {
// 创建新节点
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
newNode->next = NULL;
// 如果链表为空,新节点即为头节点
if (list->head == NULL) {
newNode->prev = NULL;
list->head = newNode;
return;
}
// 找到链表末尾节点
struct Node* current = list->head;
while (current->next != NULL) {
current = current->next;
}
// 将新节点插入到末尾
current->next = newNode;
newNode->prev = current;
}
// 在链表指定位置插入节点
void insertAtPosition(struct LinkedList* list, int data, int position) {
// 创建新节点
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = data;
// 如果链表为空或插入位置为0,新节点即为头节点
if (list->head == NULL || position == 0) {
newNode->prev = NULL;
newNode->next = list->head;
if (list->head != NULL) {
list->head->prev = newNode;
}
list->head = newNode;
return;
}
// 找到插入位置的前一个节点
struct Node* current = list->head;
for (int i = 0; i < position - 1 && current != NULL; i++) {
current = current->next;
}
// 如果插入位置超过链表长度,直接插入到末尾
if (current == NULL) {
insertAtEnd(list, data);
return;
}
// 插入新节点
newNode->prev = current;
newNode->next = current->next;
if (current->next != NULL) {
current->next->prev = newNode;
}
current->next = newNode;
}
// 从链表中删除指定位置的节点
void deleteAtPosition(struct LinkedList* list, int position) {
// 如果链表为空,无需删除
if (list->head == NULL) {
return;
}
// 找到要删除的节点
struct Node* current = list->head;
for (int i = 0; i < position && current != NULL; i++) {
current = current->next;
}
// 如果删除位置超过链表长度,无需删除
if (current == NULL) {
return;
}
// 调整前后节点的指针
if (current->prev != NULL) {
current->prev->next = current->next;
} else {
list->head = current->next; // 被删除的是头节点
}
if (current->next != NULL) {
current->next->prev = current->prev;
}
// 释放被删除节点的内存
free(current);
}
// 打印双链表的所有节点
void printList(struct LinkedList* list) {
struct Node* current = list->head;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 释放链表内存
void freeList(struct LinkedList* list) {
struct Node* current = list->head;
struct Node* next;
while (current != NULL) {
next = current->next;
free(current);
current = next;
}
list->head = NULL;
}
int main() {
// 创建一个双链表
struct LinkedList myList;
initializeList(&myList);
// 在末尾插入节点
insertAtEnd(&myList, 1);
insertAtEnd(&myList, 2);
insertAtEnd(&myList, 3);
// 打印链表:1 2 3
printf("双链表:");
printList(&myList);
// 在指定位置插入节点
insertAtPosition(&myList, 4, 1);
// 打印链表:1 4 2 3
printf("插入节点后的双链表:");
printList(&myList);
// 删除指定位置的节点
deleteAtPosition(&myList, 2);
// 打印链表:1 4 3
printf("删除节点后的双链表:");
printList(&myList);
// 释放链表内存
freeList(&myList);
return 0;
}
双循环链表
#include <stdio.h>
#include <stdlib.h>
// 定义双循环链表节点结构
struct Node {
int data; // 节点数据
struct Node *next; // 指向下一个节点的指针
struct Node *prev; // 指向前一个节点的指针
};
// 初始化双循环链表
struct Node* initializeList() {
// 创建头节点
struct Node *head = (struct Node*)malloc(sizeof(struct Node));
if (head == NULL) {
printf("内存分配失败\n");
exit(1);
}
head->data = -1; // 头节点不存储数据
head->next = head; // 初始时,头节点的下一个节点指向自身
head->prev = head; // 初始时,头节点的前一个节点指向自身
return head;
}
// 在链表末尾插入新节点
void insertAtEnd(struct Node *head, int data) {
// 创建新节点
struct Node *newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(1);
}
newNode->data = data;
newNode->next = head; // 新节点的下一个节点指向头节点
newNode->prev = head->prev; // 新节点的前一个节点指向原先的最后一个节点
// 更新原先最后一个节点的 next 指针,指向新节点
head->prev->next = newNode;
// 更新头节点的 prev 指针,指向新节点
head->prev = newNode;
}
// 打印双循环链表的内容
void printList(struct Node *head) {
struct Node *current = head->next; // 从第一个节点开始打印
while (current != head) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 释放链表的内存
void freeList(struct Node *head) {
struct Node *current = head->next;
while (current != head) {
struct Node *temp = current;
current = current->next;
free(temp);
}
free(head); // 释放头节点的内存
}
int main() {
// 初始化双循环链表
struct Node *myList = initializeList();
// 在链表末尾插入一些数据
insertAtEnd(myList, 1);
insertAtEnd(myList, 2);
insertAtEnd(myList, 3);
// 打印链表内容
printf("链表内容:");
printList(myList);
// 释放链表内存
freeList(myList);
return 0;
}
静态链表
#include <stdio.h>
#define MAX_SIZE 100
// 定义静态链表节点
struct Node {
int data; // 数据域
int next; // 指针域,表示下一个节点在数组中的下标
};
// 定义静态链表
struct Node staticList[MAX_SIZE];
// 初始化静态链表
void initStaticList() {
for (int i = 0; i < MAX_SIZE - 1; ++i) {
staticList[i].next = i + 1; // 链接各节点,最后一个节点的next为0表示结束
}
staticList[MAX_SIZE - 1].next = 0;
}
// 在静态链表的指定位置插入节点
int insertNode(int value, int position) {
// 判断位置是否合法
if (position < 1 || position > MAX_SIZE - 1) {
printf("插入位置不合法\n");
return 0;
}
int newNodeIndex = staticList[0].next; // 获取空闲节点的下标
if (newNodeIndex != 0) {
// 空闲节点不为0,表示有可用节点
staticList[0].next = staticList[newNodeIndex].next; // 更新空闲节点链表头
staticList[newNodeIndex].data = value; // 赋值数据
int i = 1;
int current = 0; // 从头节点开始
// 找到插入位置的前一个节点
while (i < position) {
current = staticList[current].next;
++i;
}
staticList[newNodeIndex].next = staticList[current].next; // 新节点指向下一个节点
staticList[current].next = newNodeIndex; // 前一个节点指向新节点
return 1;
} else {
printf("链表已满,无法插入\n");
return 0;
}
}
// 在静态链表的指定位置删除节点
int deleteNode(int position) {
// 判断位置是否合法
if (position < 1 || position > MAX_SIZE - 1) {
printf("删除位置不合法\n");
return 0;
}
int i = 1;
int current = 0; // 从头节点开始
// 找到删除位置的前一个节点
while (i < position) {
current = staticList[current].next;
++i;
}
int deleteNodeIndex = staticList[current].next; // 获取要删除的节点下标
staticList[current].next = staticList[deleteNodeIndex].next; // 前一个节点指向删除节点的下一个节点
staticList[deleteNodeIndex].next = staticList[0].next; // 删除节点指向空闲节点链表头
staticList[0].next = deleteNodeIndex; // 更新空闲节点链表头
return 1;
}
// 打印静态链表
void printStaticList() {
int current = staticList[MAX_SIZE - 1].next; // 从第一个有效节点开始
while (current != 0) {
printf("%d ", staticList[current].data);
current = staticList[current].next;
}
printf("\n");
}
int main() {
initStaticList(); // 初始化静态链表
// 插入节点
insertNode(1, 1);
insertNode(2, 2);
insertNode(3, 3);
insertNode(4, 2);
printf("插入节点后的链表:\n");
printStaticList();
// 删除节点
deleteNode(2);
printf("删除节点后的链表:\n");
printStaticList();
return 0;
}
栈与队列篇
队列
#include <stdio.h>
#include <stdlib.h>
typedef struct QNode{ // 定义一个结构体数据
int data; // 数据域
struct QNode* next; // 指针域
}QNode; // struct QNode 的别名
QNode *initQueue() // 定义一个指向 Qnode型 的 指针函数
{
QNode *queue = malloc(sizeof(QNode)); // 使用 malloc 函数为 *queue 分配空间
queue->next = NULL; // 使 *next 指针为 NULL
return queue; // 返回 的是一个 指针
}
// 入队
QNode *enQueue(QNode* rear,int data)
{
QNode *enElem = malloc(sizeof(QNode)); // 从 结尾元素 开始
enElem->data = data; // 结尾元素 为 1
enElem->next = NULL; // 结尾元素 next 为 NULL
rear->next = enElem; // 尾指针 的指针域 指向 新结点
rear = enElem; // 尾指针 指向 新结点
return rear; // 返回新的 尾指针
}
void deQueue(QNode* top,QNode** rear) // 出队(二级指针,在函数中更改指针的指向不能直接更改,要使用指向指针的指针)
{
QNode *p = NULL; // 创建新的 结构体指针 p
if(top->next == NULL){ // 通过判断头结点 的 指针域 是否为空 来判读队列是否为空
printf("队列为空 ");
return;
}
p = top->next; // 赋值
printf("%d ",p->data); // 打印
top->next = p->next; // 删除结点
if(*rear == p){ // 判断删除的 是否 是最后一个结点,如果是的话,rear指向头结点,避免 rear 成为野指针
*rear = top;
}
free(p); //
}
int main()
{
QNode *top = NULL, *rear = NULL; // 队头指针 和 队尾指针初始化
top = rear = initQueue(); // 队头指针 和 队尾指针 指向头结点
// 入栈
rear = enQueue(rear,1); // 传入的数据是queue
rear = enQueue(rear,2);
// 出栈
deQueue(top, &rear); // 1
deQueue(top, &rear); // 2
// 没有元素,看会显示什么
deQueue(top, &rear);
// 入栈
rear = enQueue(rear,4);
rear = enQueue(rear,5);
rear = enQueue(rear,6);
// 出栈
deQueue(top, &rear); // 4
deQueue(top, &rear); // 5
return 0;
}
循环队列
#include <stdio.h>
#include <stdlib.h>
// 定义循环队列结构
#define MAX_SIZE 5 // 队列的最大容量
typedef struct {
int *array; // 存储队列元素的数组
int front; // 队头指针
int rear; // 队尾指针
int capacity; // 队列容量
} CircularQueue;
// 初始化循环队列
CircularQueue* initQueue(int size) {
CircularQueue *queue = (CircularQueue*)malloc(sizeof(CircularQueue));
if (!queue) {
printf("内存分配失败\n");
exit(EXIT_FAILURE);
}
queue->array = (int*)malloc(size * sizeof(int));
if (!queue->array) {
printf("内存分配失败\n");
exit(EXIT_FAILURE);
}
queue->front = -1;
queue->rear = -1;
queue->capacity = size;
return queue;
}
// 判断队列是否为空
int isEmpty(CircularQueue *queue) {
return (queue->front == -1 && queue->rear == -1);
}
// 判断队列是否已满
int isFull(CircularQueue *queue) {
return ((queue->rear + 1) % queue->capacity == queue->front);
}
// 入队操作
void enqueue(CircularQueue *queue, int data) {
// 检查队列是否已满
if (isFull(queue)) {
printf("队列已满,无法入队\n");
return;
}
// 如果队列为空,初始化队头和队尾
if (isEmpty(queue)) {
queue->front = 0;
queue->rear = 0;
} else {
// 队尾指针后移,考虑循环
queue->rear = (queue->rear + 1) % queue->capacity;
}
// 将数据入队
queue->array[queue->rear] = data;
printf("入队:%d\n", data);
}
// 出队操作
int dequeue(CircularQueue *queue) {
// 检查队列是否为空
if (isEmpty(queue)) {
printf("队列为空,无法出队\n");
exit(EXIT_FAILURE);
}
// 获取出队元素
int data = queue->array[queue->front];
// 如果队列只有一个元素,出队后将队头和队尾重置
if (queue->front == queue->rear) {
queue->front = -1;
queue->rear = -1;
} else {
// 队头指针后移,考虑循环
queue->front = (queue->front + 1) % queue->capacity;
}
printf("出队:%d\n", data);
return data;
}
// 打印队列元素
void printQueue(CircularQueue *queue) {
if (isEmpty(queue)) {
printf("队列为空\n");
return;
}
printf("队列元素:");
int i = queue->front;
do {
printf("%d ", queue->array[i]);
i = (i + 1) % queue->capacity;
} while (i != (queue->rear + 1) % queue->capacity);
printf("\n");
}
// 释放队列内存
void freeQueue(CircularQueue *queue) {
free(queue->array);
free(queue);
}
int main() {
CircularQueue *queue = initQueue(MAX_SIZE);
enqueue(queue, 1);
enqueue(queue, 2);
enqueue(queue, 3);
printQueue(queue);
dequeue(queue);
printQueue(queue);
enqueue(queue, 4);
enqueue(queue, 5);
enqueue(queue, 6); // 队列已满,无法入队
printQueue(queue);
freeQueue(queue);
return 0;
}
双端队列
#include <stdio.h>
#include <stdlib.h>
// 定义双端队列节点
struct Node {
int data;
struct Node* next;
struct Node* prev;
};
// 定义双端队列结构体
struct Deque {
struct Node* front; // 队头指针
struct Node* rear; // 队尾指针
};
// 初始化双端队列
void initializeDeque(struct Deque* deque) {
deque->front = NULL;
deque->rear = NULL;
}
// 在队头插入元素
void insertFront(struct Deque* deque, int data) {
// 创建新节点
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败\n");
return;
}
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
if (deque->front == NULL) {
// 队列为空,新节点成为队头和队尾
deque->front = newNode;
deque->rear = newNode;
} else {
// 队头插入新节点
newNode->next = deque->front;
deque->front->prev = newNode;
deque->front = newNode;
}
}
// 在队尾插入元素
void insertRear(struct Deque* deque, int data) {
// 创建新节点
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
if (newNode == NULL) {
printf("内存分配失败\n");
return;
}
newNode->data = data;
newNode->next = NULL;
newNode->prev = NULL;
if (deque->rear == NULL) {
// 队列为空,新节点成为队头和队尾
deque->front = newNode;
deque->rear = newNode;
} else {
// 队尾插入新节点
newNode->prev = deque->rear;
deque->rear->next = newNode;
deque->rear = newNode;
}
}
// 在队头删除元素
void deleteFront(struct Deque* deque) {
if (deque->front == NULL) {
printf("队列为空\n");
} else {
struct Node* temp = deque->front;
deque->front = deque->front->next;
if (deque->front == NULL) {
// 队列只有一个元素,删除后为空队列
deque->rear = NULL;
} else {
// 更新新队头的prev指针
deque->front->prev = NULL;
}
free(temp);
}
}
// 在队尾删除元素
void deleteRear(struct Deque* deque) {
if (deque->rear == NULL) {
printf("队列为空\n");
} else {
struct Node* temp = deque->rear;
deque->rear = deque->rear->prev;
if (deque->rear == NULL) {
// 队列只有一个元素,删除后为空队列
deque->front = NULL;
} else {
// 更新新队尾的next指针
deque->rear->next = NULL;
}
free(temp);
}
}
// 打印双端队列元素
void printDeque(struct Deque* deque) {
struct Node* current = deque->front;
while (current != NULL) {
printf("%d ", current->data);
current = current->next;
}
printf("\n");
}
// 释放双端队列内存
void freeDeque(struct Deque* deque) {
struct Node* current = deque->front;
while (current != NULL) {
struct Node* temp = current;
current = current->next;
free(temp);
}
deque->front = NULL;
deque->rear = NULL;
}
int main() {
struct Deque myDeque;
initializeDeque(&myDeque);
insertFront(&myDeque, 1);
insertFront(&myDeque, 2);
insertRear(&myDeque, 3);
printf("双端队列元素:");
printDeque(&myDeque);
deleteFront(&myDeque);
printf("删除队头后的元素:");
printDeque(&myDeque);
deleteRear(&myDeque);
printf("删除队尾后的元素:");
printDeque(&myDeque);
freeDeque(&myDeque);
return 0;
}
链表栈
#include <stdio.h>
#include <stdlib.h>
// 定义栈结构
struct Stack {
int capacity; // 栈的容量
int top; // 栈顶索引
int *array; // 存储元素的数组
};
// 创建一个新栈
struct Stack *createStack(int capacity) {
struct Stack *stack = (struct Stack *)malloc(sizeof(struct Stack));
if (!stack) {
printf("内存分配失败\n");
exit(EXIT_FAILURE);
}
stack->capacity = capacity;
stack->top = -1; // 初始化栈顶为-1
stack->array = (int *)malloc(stack->capacity * sizeof(int));
if (!stack->array) {
printf("内存分配失败\n");
exit(EXIT_FAILURE);
}
return stack;
}
// 判断栈是否为空
int isEmpty(struct Stack *stack) {
return stack->top == -1;
}
// 判断栈是否已满
int isFull(struct Stack *stack) {
return stack->top == stack->capacity - 1;
}
// 入栈操作
void push(struct Stack *stack, int item) {
if (isFull(stack)) {
printf("栈已满,无法入栈\n");
return;
}
stack->array[++stack->top] = item;
printf("%d 入栈\n", item);
}
// 出栈操作
int pop(struct Stack *stack) {
if (isEmpty(stack)) {
printf("栈为空,无法出栈\n");
exit(EXIT_FAILURE);
}
int item = stack->array[stack->top--];
printf("%d 出栈\n", item);
return item;
}
// 获取栈顶元素
int peek(struct Stack *stack) {
if (isEmpty(stack)) {
printf("栈为空\n");
exit(EXIT_FAILURE);
}
return stack->array[stack->top];
}
// 主函数用于测试栈操作
int main() {
struct Stack *stack = createStack(5);
push(stack, 10);
push(stack, 20);
push(stack, 30);
printf("栈顶元素:%d\n", peek(stack));
pop(stack);
pop(stack);
printf("栈是否为空:%s\n", isEmpty(stack) ? "是" : "否");
return 0;
}
数组栈
#include <stdio.h>
typedef int DataType; //定义 数据域类型
#define MaxStackSize 64 //定义 数据域数组 最大长度
#define OK 1
#define ERROR 0
typedef struct
{
DataType stack[MaxStackSize]; // 定义一个 结构体数组
int top; // 定义 栈顶 的高度
}SeqStack;
void StackInit(SeqStack *S) // 初始化, 栈高度为0
{
S->top = 0;
}
int StackIsEmpty(SeqStack *S) // 判断是否 栈空
{
if (S->top <= 0)
{
printf("栈为空!!!");
return ERROR;
}
else
return OK;
}
int StackPush(SeqStack *S, DataType x) // 入栈
{
if (S->top >= MaxStackSize) // 判断 栈的高度>=宏定义 的 最大容纳
{
printf("栈满,无法进栈!!!\n");
return ERROR;
}
else
{
S->stack[S->top] = x; // 将x赋值给 结点S 的 数组中
S->top++; // 执行一次,top+1
return OK;
}
}
int StackPop(SeqStack *S, DataType *x) // 出栈
{
if (S->top <= 0) // 先判断 top 是否为 空
{
printf("堆栈已空,无法出栈!!!\n");
return ERROR;
}
else
{
S->top--; // S结构体 下的 top 获取 栈顶至栈底 的下标
*x = S->stack[S->top]; // 通过 指针 获取数据,返回 相应值
return OK;
}
}
int StackGetTop(SeqStack *S, DataType *x) // 获取 栈顶 元素
{
if (S->top <= 0) // 先判断 top 是否为 空
{
printf("堆栈已空!!!\n");
return ERROR;
}
else
{
*x = S->stack[S->top - 1]; // 找到 数组栈顶 的 下标 [top-1]
return 1;
}
}
int main()
{
SeqStack myStack;
int i, x;
StackInit(&myStack);
for (i = 0; i < 10; i++)
{
StackPush(&myStack, i + 1);
}
StackGetTop(&myStack, &x);
printf("当前栈顶元素为:%d\n", x);
printf("依次出栈:");
while (StackIsEmpty(&myStack))
{
StackPop(&myStack, &x);
printf("%d\t", x);
}
printf("\n");
return 0;
}
串篇
链表串
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
// 定义字符串结构体
typedef struct {
char *data; // 字符数组指针
int length; // 字符串长度
} String;
// 初始化字符串
void initString(String *str, const char *initial) {
str->length = strlen(initial);
str->data = (char *)malloc((str->length + 1) * sizeof(char)); // +1 用于存储字符串结束符 '\0'
if (str->data == NULL) {
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
strcpy(str->data, initial);
}
// 获取字符串长度
int getStringLength(const String *str) {
return str->length;
}
// 连接字符串
void concatenateString(String *result, const String *str1, const String *str2) {
result->length = str1->length + str2->length;
result->data = (char *)malloc((result->length + 1) * sizeof(char));
if (result->data == NULL) {
fprintf(stderr, "内存分配失败\n");
exit(EXIT_FAILURE);
}
strcpy(result->data, str1->data);
strcat(result->data, str2->data);
}
// 释放字符串内存
void freeString(String *str) {
free(str->data);
str->data = NULL;
str->length = 0;
}
int main() {
// 创建并初始化字符串
String myString;
initString(&myString, "Hello, ");
// 获取字符串长度
printf("字符串长度: %d\n", getStringLength(&myString));
// 连接字符串
String anotherString;
initString(&anotherString, "World!");
String resultString;
concatenateString(&resultString, &myString, &anotherString);
// 打印连接后的字符串
printf("连接后的字符串: %s\n", resultString.data);
// 释放字符串内存
freeString(&myString);
freeString(&anotherString);
freeString(&resultString);
return 0;
}
KMP前缀表计算函数
void computeLPSArray(char *pattern, int M, int *lps) {
int len = 0; // 已匹配的前缀长度
int i = 1;
while (i < M) {
if (pattern[i] == pattern[len]) {
len++;
lps[i] = len;
i++;
} else {
if (len != 0) {
len = lps[len - 1];
} else {
lps[i] = 0;
i++;
}
}
}
}
暴力匹配函数
int ViDlentMatch(String *master,String *sub){
int i=0,j=0;
while(i<master->len&&j<sub->len){
if(master->data[i]==sub->data[j]){
j++;
i++;
}
else{
i=i-j+1;
j=0;
}
}
}
树篇
二叉树
#include <stdio.h>
#include <stdlib.h>
// 定义二叉树节点结构
struct TreeNode {
int data;
struct TreeNode* left;
struct TreeNode* right;
};
// 创建新节点的函数
struct TreeNode* createNode(int value) {
struct TreeNode* newNode = (struct TreeNode*)malloc(sizeof(struct TreeNode));
if (newNode == NULL) {
printf("内存分配失败\n");
exit(EXIT_FAILURE);
}
newNode->data = value;
newNode->left = NULL;
newNode->right = NULL;
return newNode;
}
// 先序遍历二叉树
void preOrderTraversal(struct TreeNode* root) {
if (root != NULL) {
printf("%d ", root->data);
preOrderTraversal(root->left);
preOrderTraversal(root->right);
}
}
// 中序遍历二叉树
void inOrderTraversal(struct TreeNode* root) {
if (root != NULL) {
inOrderTraversal(root->left);
printf("%d ", root->data);
inOrderTraversal(root->right);
}
}
// 后序遍历二叉树
void postOrderTraversal(struct TreeNode* root) {
if (root != NULL) {
postOrderTraversal(root->left);
postOrderTraversal(root->right);
printf("%d ", root->data);
}
}
int main() {
// 创建二叉树
struct TreeNode* root = createNode(1);
root->left = createNode(2);
root->right = createNode(3);
root->left->left = createNode(4);
root->left->right = createNode(5);
// 先序遍历
printf("先序遍历结果:");
preOrderTraversal(root);
printf("\n");
// 中序遍历
printf("中序遍历结果:");
inOrderTraversal(root);
printf("\n");
// 后序遍历
printf("后序遍历结果:");
postOrderTraversal(root);
printf("\n");
return 0;
}
二叉排序树
#include <stdio.h>
#include <stdlib.h>
// 定义二叉搜索树节点
typedef struct TreeNode {
int data;
struct TreeNode *left;
struct TreeNode *right;
} TreeNode;
// 插入节点
TreeNode* insert(TreeNode* root, int value) {
if (root == NULL) {
// 如果树为空,创建新节点
TreeNode* newNode = (TreeNode*)malloc(sizeof(TreeNode));
newNode->data = value;
newNode->left = newNode->right = NULL;
return newNode;
}
// 根据值大小递归插入左子树或右子树
if (value < root->data) {
root->left = insert(root->left, value);
} else if (value > root->data) {
root->right = insert(root->right, value);
}
return root;
}
// 查找节点
TreeNode* search(TreeNode* root, int value) {
if (root == NULL || root->data == value) {
return root;
}
// 根据值大小递归查找左子树或右子树
if (value < root->data) {
return search(root->left, value);
} else {
return search(root->right, value);
}
}
// 中序遍历
void inorderTraversal(TreeNode* root) {
if (root != NULL) {
inorderTraversal(root->left);
printf("%d ", root->data);
inorderTraversal(root->right);
}
}
// 释放树节点的内存
void freeTree(TreeNode* root) {
if (root != NULL) {
freeTree(root->left);
freeTree(root->right);
free(root);
}
}
int main() {
TreeNode* root = NULL;
// 插入节点
root = insert(root, 50);
insert(root, 30);
insert(root, 20);
insert(root, 40);
insert(root, 70);
insert(root, 60);
insert(root, 80);
// 中序遍历
printf("中序遍历结果: ");
inorderTraversal(root);
printf("\n");
// 查找节点
int targetValue = 40;
TreeNode* result = search(root, targetValue);
if (result != NULL) {
printf("节点 %d 存在于树中。\n", targetValue);
} else {
printf("节点 %d 不存在于树中。\n", targetValue);
}
// 释放内存
freeTree(root);
return 0;
}
平衡二叉树
#include <stdio.h>
#include <stdlib.h>
// 定义平衡二叉树节点结构
struct AVLNode {
int data; // 节点值
int height; // 节点高度
struct AVLNode* left; // 左子树
struct AVLNode* right; // 右子树
};
// 计算节点高度
int height(struct AVLNode* node) {
if (node == NULL) return 0;
return node->height;
}
// 获取两个整数中的较大者
int max(int a, int b) {
return (a > b) ? a : b;
}
// 创建一个新节点
struct AVLNode* newNode(int data) {
struct AVLNode* node = (struct AVLNode*)malloc(sizeof(struct AVLNode));
if (node == NULL) {
printf("内存分配失败\n");
exit(1);
}
node->data = data;
node->height = 1; // 新节点高度初始化为1
node->left = NULL;
node->right = NULL;
return node;
}
// 获取节点的平衡因子
int getBalance(struct AVLNode* node) {
if (node == NULL) return 0;
return height(node->left) - height(node->right);
}
// 右旋转
struct AVLNode* rightRotate(struct AVLNode* y) {
struct AVLNode* x = y->left;
struct AVLNode* T2 = x->right;
// 执行旋转
x->right = y;
y->left = T2;
// 更新高度
y->height = max(height(y->left), height(y->right)) + 1;
x->height = max(height(x->left), height(x->right)) + 1;
return x;
}
// 左旋转
struct AVLNode* leftRotate(struct AVLNode* x) {
struct AVLNode* y = x->right;
struct AVLNode* T2 = y->left;
// 执行旋转
y->left = x;
x->right = T2;
// 更新高度
x->height = max(height(x->left), height(x->right)) + 1;
y->height = max(height(y->left), height(y->right)) + 1;
return y;
}
// 插入节点
struct AVLNode* insert(struct AVLNode* root, int data) {
// 执行标准的BST插入
if (root == NULL) return newNode(data);
if (data < root->data)
root->left = insert(root->left, data);
else if (data > root->data)
root->right = insert(root->right, data);
else // 不允许插入相同的值
return root;
// 更新节点的高度
root->height = 1 + max(height(root->left), height(root->right));
// 获取节点的平衡因子
int balance = getBalance(root);
// 进行平衡操作
// 左子树不平衡,右旋
if (balance > 1 && data < root->left->data)
return rightRotate(root);
// 右子树不平衡,左旋
if (balance < -1 && data > root->right->data)
return leftRotate(root);
// 左右子树均不平衡,先左旋再右旋
if (balance > 1 && data > root->left->data) {
root->left = leftRotate(root->left);
return rightRotate(root);
}
// 右左子树均不平衡,先右旋再左旋
if (balance < -1 && data < root->right->data) {
root->right = rightRotate(root->right);
return leftRotate(root);
}
return root;
}
// 中序遍历平衡二叉树
void inOrderTraversal(struct AVLNode* root) {
if (root != NULL) {
inOrderTraversal(root->left);
printf("%d ", root->data);
inOrderTraversal(root->right);
}
}
// 主函数
int main() {
struct AVLNode* root = NULL;
// 插入节点
root = insert(root, 10);
root = insert(root, 20);
root = insert(root, 30);
root = insert(root, 40);
root = insert(root, 50);
root = insert(root, 25);
// 中序遍历输出结果
printf("中序遍历结果:");
inOrderTraversal(root);
return 0;
}
霍夫曼树
#include <stdio.h>
#include <stdlib.h>
// 霍夫曼树节点结构
typedef struct Node {
int data; // 节点权值
struct Node *left; // 左子树指针
struct Node *right; // 右子树指针
} Node;
// 霍夫曼树节点队列结构
typedef struct QueueNode {
Node *data; // 指向霍夫曼树节点的指针
struct QueueNode *next; // 指向下一个节点的指针
} QueueNode;
// 霍夫曼树节点队列
typedef struct {
QueueNode *front; // 队列头
QueueNode *rear; // 队列尾
} Queue;
// 初始化队列
Queue* initQueue() {
Queue *q = (Queue*)malloc(sizeof(Queue));
q->front = q->rear = NULL;
return q;
}
// 入队
void enqueue(Queue *q, Node *data) {
QueueNode *newNode = (QueueNode*)malloc(sizeof(QueueNode));
newNode->data = data;
newNode->next = NULL;
if (q->rear == NULL) {
q->front = q->rear = newNode;
} else {
q->rear->next = newNode;
q->rear = newNode;
}
}
// 出队
Node* dequeue(Queue *q) {
if (q->front == NULL) {
return NULL;
}
QueueNode *temp = q->front;
Node *data = temp->data;
q->front = temp->next;
free(temp);
if (q->front == NULL) {
q->rear = NULL;
}
return data;
}
// 构建霍夫曼树
Node* buildHuffmanTree(int weights[], int n) {
// 初始化队列,将权值作为节点入队
Queue *q = initQueue();
for (int i = 0; i < n; i++) {
Node *newNode = (Node*)malloc(sizeof(Node));
newNode->data = weights[i];
newNode->left = newNode->right = NULL;
enqueue(q, newNode);
}
// 构建霍夫曼树
while (q->front != q->rear) {
Node *left = dequeue(q);
Node *right = dequeue(q);
Node *parent = (Node*)malloc(sizeof(Node));
parent->data = left->data + right->data;
parent->left = left;
parent->right = right;
enqueue(q, parent);
}
// 需要返回的根节点
return dequeue(q);
}
// 遍历霍夫曼树
void traverseHuffmanTree(Node *root) {
if (root != NULL) {
printf("%d ", root->data);
traverseHuffmanTree(root->left);
traverseHuffmanTree(root->right);
}
}
int main() {
// 例子:构建霍夫曼树的权值数组
int weights[] = {5, 9, 12, 13, 16, 45};
int n = sizeof(weights) / sizeof(weights[0]);
// 构建霍夫曼树
Node *huffmanRoot = buildHuffmanTree(weights, n);
// 遍历霍夫曼树
printf("霍夫曼树的遍历结果:\n");
traverseHuffmanTree(huffmanRoot);
return 0;
}
图篇
有向图
#include <stdio.h>
#include <stdlib.h>
// 定义图节点结构
struct Node {
int vertex;
struct Node* next;
};
// 定义图结构
struct Graph {
int numVertices;
struct Node** adjLists;
};
// 创建节点
struct Node* createNode(int v) {
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->vertex = v;
newNode->next = NULL;
return newNode;
}
// 创建图
struct Graph* createGraph(int vertices) {
struct Graph* graph = (struct Graph*)malloc(sizeof(struct Graph));
graph->numVertices = vertices;
graph->adjLists = (struct Node**)malloc(vertices * sizeof(struct Node*));
for (int i = 0; i < vertices; i++) {
graph->adjLists[i] = NULL;
}
return graph;
}
// 添加边
void addEdge(struct Graph* graph, int src, int dest) {
struct Node* newNode = createNode(dest);
newNode->next = graph->adjLists[src];
graph->adjLists[src] = newNode;
}
// 遍历图
void printGraph(struct Graph* graph) {
for (int i = 0; i < graph->numVertices; i++) {
struct Node* temp = graph->adjLists[i];
printf("顶点 %d: ", i);
while (temp) {
printf("%d -> ", temp->vertex);
temp = temp->next;
}
printf("NULL\n");
}
}
int main() {
// 创建有向图,这里示例包含 4 个顶点
struct Graph* graph = createGraph(4);
// 添加边
addEdge(graph, 0, 1);
addEdge(graph, 0, 2);
addEdge(graph, 1, 2);
addEdge(graph, 2, 0);
addEdge(graph, 2, 3);
addEdge(graph, 3, 3);
// 输出图的邻接表表示
printf("有向图的邻接表表示:\n");
printGraph(graph);
return 0;
}
无向图
#include <stdio.h>
#include <stdlib.h>
// 定义图的最大节点数
#define MAX_NODES 100
// 定义邻接表中的链表节点
struct Node {
int data;
struct Node* next;
};
// 定义图结构
struct Graph {
int numNodes;
struct Node* adjList[MAX_NODES];
};
// 初始化图
void initializeGraph(struct Graph* graph, int numNodes) {
graph->numNodes = numNodes;
for (int i = 0; i < numNodes; i++) {
graph->adjList[i] = NULL;
}
}
// 添加边到图中
void addEdge(struct Graph* graph, int src, int dest) {
// 添加从源节点到目标节点的边
struct Node* newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = dest;
newNode->next = graph->adjList[src];
graph->adjList[src] = newNode;
// 由于是无向图,还要添加从目标节点到源节点的边
newNode = (struct Node*)malloc(sizeof(struct Node));
newNode->data = src;
newNode->next = graph->adjList[dest];
graph->adjList[dest] = newNode;
}
// 打印图的邻接表
void printGraph(struct Graph* graph) {
for (int i = 0; i < graph->numNodes; i++) {
struct Node* currentNode = graph->adjList[i];
printf("邻接表[%d]:", i);
while (currentNode != NULL) {
printf(" -> %d", currentNode->data);
currentNode = currentNode->next;
}
printf("\n");
}
}
int main() {
// 创建一个包含5个节点的无向图
struct Graph myGraph;
initializeGraph(&myGraph, 5);
// 添加边
addEdge(&myGraph, 0, 1);
addEdge(&myGraph, 0, 4);
addEdge(&myGraph, 1, 2);
addEdge(&myGraph, 1, 3);
addEdge(&myGraph, 1, 4);
addEdge(&myGraph, 2, 3);
addEdge(&myGraph, 3, 4);
// 打印图的邻接表
printGraph(&myGraph);
return 0;
}
加权图
#include <stdio.h>
#include <stdlib.h>
#define MAX_NODES 100
// 定义图的结构体
typedef struct {
int vertices; // 节点数
int edges[MAX_NODES][MAX_NODES]; // 邻接矩阵表示边的权重
} WeightedGraph;
// 初始化图
void initializeGraph(WeightedGraph *graph, int vertices) {
graph->vertices = vertices;
// 初始化邻接矩阵,所有边的权重初始化为0
for (int i = 0; i < vertices; i++) {
for (int j = 0; j < vertices; j++) {
graph->edges[i][j] = 0;
}
}
}
// 添加带权重的边
void addEdge(WeightedGraph *graph, int start, int end, int weight) {
// 在邻接矩阵中表示边的权重
graph->edges[start][end] = weight;
graph->edges[end][start] = weight; // 如果是无向图,边是双向的
}
// 打印图的邻接矩阵
void printGraph(WeightedGraph *graph) {
printf("加权图的邻接矩阵表示:\n");
for (int i = 0; i < graph->vertices; i++) {
for (int j = 0; j < graph->vertices; j++) {
printf("%d ", graph->edges[i][j]);
}
printf("\n");
}
}
int main() {
WeightedGraph graph;
int vertices = 5;
initializeGraph(&graph, vertices);
// 添加带权重的边
addEdge(&graph, 0, 1, 2);
addEdge(&graph, 0, 2, 4);
addEdge(&graph, 1, 2, 1);
addEdge(&graph, 1, 3, 7);
addEdge(&graph, 2, 4, 3);
addEdge(&graph, 3, 4, 5);
// 打印图的邻接矩阵
printGraph(&graph);
return 0;
}
无向完全图
#include <stdio.h>
// 定义图的最大节点数
#define MAX_NODES 5
// 定义图的邻接矩阵
int adjacencyMatrix[MAX_NODES][MAX_NODES];
// 初始化图的邻接矩阵
void initializeGraph() {
for (int i = 0; i < MAX_NODES; i++) {
for (int j = 0; j < MAX_NODES; j++) {
adjacencyMatrix[i][j] = 0;
}
}
}
// 添加边,构建无向完全图
void addEdge(int startNode, int endNode) {
adjacencyMatrix[startNode][endNode] = 1;
adjacencyMatrix[endNode][startNode] = 1;
}
// 打印邻接矩阵
void printGraph() {
printf("无向完全图的邻接矩阵表示:\n");
for (int i = 0; i < MAX_NODES; i++) {
for (int j = 0; j < MAX_NODES; j++) {
printf("%d ", adjacencyMatrix[i][j]);
}
printf("\n");
}
}
int main() {
// 初始化图
initializeGraph();
// 添加边,构建无向完全图
for (int i = 0; i < MAX_NODES; i++) {
for (int j = i + 1; j < MAX_NODES; j++) {
addEdge(i, j);
}
}
// 打印图的邻接矩阵
printGraph();
return 0;
}
有向完全图
#include <stdio.h>
// 定义图的节点数量
#define V 4
// 打印有向完全图的邻接矩阵
void printDirectedCompleteGraph(int graph[V][V]) {
printf("有向完全图的邻接矩阵:\n");
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
printf("%d ", graph[i][j]);
}
printf("\n");
}
}
// 创建有向完全图的邻接矩阵
void createDirectedCompleteGraph(int graph[V][V]) {
for (int i = 0; i < V; i++) {
for (int j = 0; j < V; j++) {
// 对角线上的元素为 0,表示不允许节点指向自己
// 其他位置的元素为 1,表示存在有向边
if (i == j) {
graph[i][j] = 0;
} else {
graph[i][j] = 1;
}
}
}
}
int main() {
// 创建有向完全图的邻接矩阵
int graph[V][V];
createDirectedCompleteGraph(graph);
// 打印图的结构
printDirectedCompleteGraph(graph);
return 0;
}
查找和排序
查找系列
顺序查找(哨兵版)
#include<stdio.h>
int array[10] = { 1,2,3,4,5,6,7,8,9,10 };
int sequential_search2(int arr[], int len, int n) {
arr[0] = n;//这里arr[0]就是哨兵,
int i = n;
while (arr[i] != n)
{
--i;
}
return i;//如果返回0这表明没有这个数据
}
void main(void) {
int s = sequential_search2(array, 10, 9);
printf("%d", s);
}
折半查找
#include<stdio.h>
int array[] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 };
int main() {
int s = 1;
int min = 0, max = sizeof(array) / sizeof(array[0]);
while (max > min) {
int mid = (max + min) / 2;
if (array[mid] == s) {
printf("找到了,在数组{%d}中", array[mid]);
return 0;
}
if (s > array[mid])
min = mid;
else
max = mid;
}
return 0;
}
插值查找
#include<stdio.h>
int InsertionSearch_iteration(int a[], int value, int n){
int low, high, mid = 0;//这里的low、high、mid是索引值
low = 0;
high = n - 1;
if (value > a[high] || value < a[low])//不在范围直接返回
return -1;
while (low <= high)//当low == high 为最后一次检查,如果还不相同则返回{
mid = low + (value - a[low]) / (a[high] - a[low]) * (high - low); //mid的计算方式改变一下即可
//如果low + high等于奇数,向下取整。也就是左边
if (a[mid] == value)//匹配到直接返回
return mid;
if (a[mid] > value)//如果是最后一次,即left==right,则high<low
high = mid - 1;//跳出while大循环
else
low = mid + 1;
}
return -1;
}
int InsertionSearch_recursive(int a[], int value, int low, int high){
if (value > a[high] || value < a[low])//不在范围直接返回
return -1;
int mid = low + (value - a[low]) / (a[high] - a[low]) * (high - low);//更改一下递推关系式
if (a[mid] == value)//递归的终止条件,前提是value在数组中存在
return mid;
if (low == high)//这已经是最后一次查找了,这一次value还不相同,则返回
{//由于只会往某种特定的方向递归,而不会出现先左递归回溯再后右递归回溯,所以low==high 是递归的最后一次。由于上面一个语句已经检查了,如果没有直接返回,那么就意味着value不相同,直接返回-1即可
return -1;
}
if (a[mid] > value)//只会往某种特定的方向递归,而不会出现先左递归后右递归
return InsertionSearch_recursive(a, value, low, mid - 1);
else
return InsertionSearch_recursive(a, value, mid + 1, high);
}
void main(){
int a[] = { 0,1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };//数组一定是顺序的
//待搜索的数据集中包含value值
int recv_iter = InsertionSearch_iteration(a, 13, 15);//找到返回索引值,否则-1
int recv_recu = InsertionSearch_recursive(a, 20, 0, 16 - 1);
if (recv_iter != -1){
printf("\nrecv_iter offset position is :%d", recv_iter);
}
else{
printf("\nrecv_iter can not find value \n");
}
if (recv_recu != -1){
printf("\nrecv_recu offset position is :%d", recv_recu);
}
else{
printf("\nrecv_recu can not find value \n");
}
//待搜索的数据集中没有包含value值
int recv_iter_1 = InsertionSearch_iteration(a, 16, 16);//找到返回索引值,否则-1
int recv_recu_1 = InsertionSearch_recursive(a, 16, 0, 16 - 1);
if (recv_iter_1 != -1){
printf("\nrecv_iter_1 offset position is :%d", recv_iter_1);
}
else{
printf("\nrecv_iter_1 can not find value \n");
}
if (recv_recu_1 != -1){
printf("recv_recu_1 offset position is :%d", recv_recu_1);
}
else{
printf("recv_recu_1 can not find value \n");
}
system("pause");
}
斐波那契查找
#include<stdio.h>
#define ERROR -1
int array[15] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15 };
/* 斐波那契查找 */
// n != MAXSIZE
//后续将不满的数值补全时,
//需用到a[n](a[n]应为一个在查找当中的的值,一个确定的值),若 n = MAXSIZE,该值不确定,不符要求
int Fibonacci_Search(int* a, int n, int key) {
int F[100]; //斐波那契数列
F[0] = 0;
F[1] = 1;
for (int i = 2; i < 100; i++)
F[i] = F[i - 1] + F[i - 2];
int low, high, mid, k = 0;
low = 1; //最低下标
high = n; //最高下标
while (n > F[k] - 1) //计算n位斐波那契数列的位置
k++;
for (int i = n; i < F[k] - 1; i++) //将不满的数值补齐
a[i] = a[n];
while (low <= high) {
mid = low + F[k - 1] - 1; //计算当前分隔的下标
if (key < a[mid]) { //查找值小于当前分隔值
high = mid - 1;
k--;
}
else if (key > a[mid]) { //查找值大于当前分隔值
low = mid + 1;
k -= 2;
}
else {
if (mid <= n)
return mid;
else
return n; //mid > n说明是补全数值,返回n
}
}
return ERROR;
}
void main() {
int s = Fibonacci_Search(array, 15, 10);
printf("%d", s);
}
哈希查找
#include<stdio.h>
#define M 6
#define P (M+1)
typedef struct HashTable{
int key; //关键字
int EmptyFlag;//占用(冲突)标志,0表示没被占用,1表示被占用
}HashTable;
void CreateHashTable(HashTable* tbl, int* data, int m, int p);
int SearchHashTable(HashTable* tbl, int key, int p);
int main(){
HashTable HashTbl[P];
int data[M] = { 10, 8, 14, 15, 20, 31 };
int i, loc;
printf("初始数据:\n");
for (i = 0; i < M; i++){
printf("data[%d] = %5d\n", i, data[i]);
}
printf("\n");
CreateHashTable(HashTbl, data, M, P);
printf("哈希表: \n");
for (i = 0; i < M; i++){
printf("tbl[%d] = %5d\n", i, HashTbl[i].key);
}
printf("\n");
for (i = 0; i < M; i++){
loc = SearchHashTable(HashTbl, data[i], P);
printf("%5d 's loc = %5d\n", data[i], loc);
}
return 0;
}
void CreateHashTable(HashTable* tbl, int* data, int m, int p){
int i, addr, k;
for (i = 0; i < p; i++) //把哈希表被占用标志置为0
tbl[i].EmptyFlag = 0;
for (i = 0; i < m; i++){
addr = data[i] % p;//计算哈希地址
k = 0;//记录冲突次数
while (k++ < p){
if (tbl[addr].EmptyFlag == 0){
tbl[addr].EmptyFlag = 1;//表示该位置已经被占用
tbl[addr].key = data[i];
break;
}
else
addr = (addr + 1) % p; //处理冲突
}
}
}
int SearchHashTable(HashTable* tbl, int key, int p){
int addr, k, loc;//loc表示查找位置下标,如果为0则表示查找失败
addr = key % P;//计算Hash地址
loc = -1;
k = 0;//记录冲突次数
while (k++ < p){
if (tbl[addr].key == key){
loc = addr;
break;
}
else
addr = (addr + 1) % p; //处理冲突
}
return loc;
}
分块查找
#include <stdio.h>
#include <stdlib.h>
struct index {//定义索引表
int key;//最大关键字
int start;//起始地址
}newIndex[3]; //定义结构体数组
int cmp(const void* a, const void* b) {
return (*(struct index*)a).key > (*(struct index*)b).key ? 1 : -1;
}
int search(int key, int a[])
{
int i = 0, startvalue;
while (i < 3 && newIndex[i].key < key)
{
i++;
}
if (i > 3)
{
return -1;
}
startvalue = newIndex[i].start;
while (startvalue <= startvalue + 5 && a[startvalue] != key)
{
startvalue++;
}
if (startvalue > startvalue + 5)
{
return -1;
}
return startvalue;
}
int main()
{
int array[] = { 1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20 };
int i, j = 1, k, key, n;
//确定模块的起始值和最大值
for (i = 0; i < 3; i++){
newIndex[i].start = j + 1;
j += 6;
for (int k = newIndex[i].start; k <= j; k++){
if (newIndex[i].key < array[k])
newIndex[i].key = array[k];
}
}
//对结构体按照key值进行排序
qsort(newIndex, 3, sizeof(newIndex[0]), cmp);
//输入要查询的数,并调用函数进行查找
printf("输入您想要查找的数:\n");
scanf_s("%d", &key);
k = search(key, array);
//输出查找结果
if (k > 0)
printf("查找成功!您要找的数在数组中的位置是:%d\n", k);
else
printf("查找失败!您查找的数不在数组中。\n");
return 0;
}
二叉树查找
#include<stdio.h>
#include<stdlib.h>
typedef struct TreeNode{
int data;
struct TreeNode* lchild;
struct TreeNode* rchild;
}TreeNode;
void Recursion(TreeNode* T){
if (T == NULL)
return;
printf("%d ", T->data);
Recursion(T->lchild);
Recursion(T->rchild);
}
void CreatTree(TreeNode** T, int data){
if (*T == NULL) {
*T = (TreeNode*)malloc(sizeof(TreeNode));
(*T)->data = data;
(*T)->lchild = NULL;
(*T)->rchild = NULL;
}
else if (data == (*T)->data)
return;
else if (data < (*T)->data)
CreatTree(&((*T)->lchild), data);
else
CreatTree(&((*T)->rchild), data);
}
TreeNode* Search(TreeNode* T, int data){
if (T->data == data)
return T;
else if (T->data > data)
return Search(T->lchild, data);
else
return Search(T->rchild, data);
}
int main(){
TreeNode* T = NULL;
int nums[] = { 4,5,19,23,2,8 };
for (int i = 0; i < 6; i++)
CreatTree(&T, nums[i]);
Recursion(T);
printf("\n");
TreeNode* ret = Search(T, 5);
printf("%d ", ret->data);
system("pause");
return 0;
}
排序系列
冒泡排序
#include<stdio.h>
int array[] = { 1,4,45,34,88,99,3,99,77,323,2,1,2545,25,26,2,323,4562,7855,3822,3656,236,263,27,37,283,377,2542,23,232,32,138,345 };
int lenght = sizeof(array) / sizeof(array[0]);
int main() {
for (int i = 0; i < lenght; i++) {
for (int j = i + 1; j < lenght; j++)
if (array[i] == array[j])
continue;
else if (array[i] > array[j])
continue;
else if (array[i] < array[j]) {
int max = array[i];
array[i] = array[j];
array[j] = max;
}
}
//在最坏的情况下,时间复杂度为(2^n)
int j=0;
while (j < lenght) {
printf("%d\n", array[j]);
j++;
}
return 0;
}
选择排序
#include<stdio.h>
int array[] = { 1,2545,25,26,2,323,4562,7855,3822,3656,236,263,27,37,283,377,2542,23,232,32,138,345 };
int lenght = sizeof(array) / sizeof(array[0]);
void main(void) {
for (int i = 0; i < lenght; i++) {
int min = i;
for (int j = i + 1; j < lenght; j++) {
if (array[j] < array[min])
min = j;
}
int tmp = array[i];
array[i] = array[min];
array[min] = tmp;
}
for (int i = 0; i < lenght; i++)
printf("%d\t", array[i]);
}
插入排序
#include<stdio.h>
int array[] = { 1,2545,25,26,2,323,4562,7855,3822,3656,236,263,27,37,283,377,2542,23,232,32,138,345 };
int lenght = sizeof(array) / sizeof(array[0]);
int main() {
for (int i = 0; i < lenght; i++) {
int tem = array[i];
int j = 0;
for (; j < i; j++) { //每次从下标0开始,和下标i比较
if (array[j] > tem) //如果array[i]小于tem,执行下方代码
break;
}
for (int k = i - 1; k >= j; k--)//通过拿到的下标,数组向后移动
array[k + 1] = array[k];
array[j] = tem;//插入
}
for (int i = 0; i < lenght; i++)
printf("%d\t", array[i]);
return 0;
}
快速排序
#include <stdio.h>
int array[] = { 1,2545,25,26,2,323,4562,7855,3822,3656,236,263,27,37,283,377,2542,23,232,32,138,345 };
int lenght = sizeof(array) / sizeof(array[0]);
void quick_sort(int num[], int low, int high) {
int i, j, temp;
int tmp;
i = low;
j = high;
tmp = num[low];
//任命为中间分界线,左边比他小,右边比他大,通常第一个元素是基准数
if (i > j) //如果下标i大于下标j,函数结束运行
return;
while (i != j) {
while (num[j] >= tmp && j > i)
j--;
while (num[i] <= tmp && j > i)
i++;
if (j > i) {
temp = num[j];
num[j] = num[i];
num[i] = temp;
}
}
num[low] = num[i];
num[i] = tmp;
quick_sort(num, low, i - 1);
quick_sort(num, i + 1, high);
}
int main(int argc, char** argv) {
quick_sort(array, 0, lenght - 1);
for (int i = 0; i < lenght; i++)
printf("%d\t", array[i]);
return 0;
}
归并排序
#include <stdio.h>
#include <stdlib.h>
// 归并排序:将一个数组中两个相邻有序空间合并成一个
// 参数说明
// a -- 包含两个有序区间的数组
// start -- 第一个有序区间的起始地址
// mid -- 第一个有序区间的结束地址。也是第二个有序区间的起始地址
// end -- 第二个有序区间的结束地址
void merge(int a[], int start, int mid, int end) {
int* tmp = (int*)malloc((end - start + 1) * sizeof(int));
// tmp是汇总2个有序区间的临时区域。
int i = start; // 第一个有序区的索引
int j = mid + 1; // 第二个有序区的索引
int k = 0; // 临时区域的索引
while (i <= mid && j <= end) {
if (a[i] <= a[j])
tmp[k++] = a[i++];
else
tmp[k++] = a[j++];
}
while (i <= mid) {
tmp[k++] = a[i++];
}
while (j <= end) {
tmp[k++] = a[j++]; // 将两个有序区间合并
}
// 排序后的元素,全部都整合到数组a中
for (i = 0; i < k; i++) {
a[start + i] = tmp[i];
}
free(tmp);
tmp = NULL;
}
// 对数组a做若干次合并:数组a的总长度为len,将它分为若干个长度为gap的数组;
// 将“没两个相邻的子数组”进项归并排序
//
// 参数说明
// a -- 待排序的数组
// len -- 数组的长度
// gap -- 子数组的长度
void merge_groups(int a[], int len, int gap) {
int i;
int len_2 = 2 * gap; // 两个相邻子数组的长度
// 将“每两个相邻的子数组”进行合并排序
for (i = 0; i + 2 * gap - 1 < len; i += (2 * gap)) {
merge(a, i, i + gap - 1, i + 2 * gap - 1);
}
// 若 i+gap-1 < len-1,则剩余一个组数组没有配对
// 将该自数字合并到已排序的数组中
if (i + gap - 1 < len - 1) {
merge(a, i, i + gap - 1, len - 1);
}
}
// 归并排序 从下往上
// 参数说明
// a -- 待排序的数组
// b -- 数组长度
void merge_sort_down_to_up(int a[], int len) {
int n;
if (a == NULL || a <= 0) {
return;
}
for (n = 1; n < len; n *= 2) {
merge_groups(a, len, n);
}
}
void main(void) {
int arr[] = { 9,5,1,6,2,3,0,4,8,7,0 };
merge_sort_down_to_up(arr, 10);
for (int i = 0; i < 10; i++)
printf("%d ", arr[i]);
}
基数排序
#define _CRT_SECURE_NO_WARNINGS 1
#include <stdio.h>
#include <stdlib.h>
// 获取a中最大值
//
// 参数说明
// a -- 数组
// b -- 数组长度
int get_max(int a[], int n) {
int i, max;
max = a[0];
for (i = 1; i < n; i++) {
if (a[i] > max) {
max = a[i];
}
}
return max;
}
// 对数组按照“某个位数”进行排序(桶排序)
//
// 参数说明
//
// a -- 数组
// n -- 数组长度
// exp -- 指数。对数组a按照指数进行排序
// 例如,对于数组a = {50,3,542,745,2014,154,63,616};
// (01)exp=1 表示按照“个位”对数组a进行排序
// (02)exp=10 表示按照“十位”对数组a进行排序
// (03)exp=100 表示按照“百位”对数组a进行排序
//
void count_sort(int a[], int n, int exp) {
int* output = (int*)malloc(n * sizeof(int)); // 存储“被排序数据”的临时数组
int i, buckets[10] = { 0 };
// 将数据出现的次数存储在buckets[]中
for (i = 0; i < n; i++) {
buckets[(a[i] / exp) % 10]++;
}
// 更改buckets[i]。目的是让改后的buckets[i]的值,是该数据在output[]中的位置。
for (i = 1; i < 10; i++) {
buckets[i] += buckets[i - 1];
}
// 将数据存储到临时数组output[]中
for (i = n - 1; i >= 0; i--) {
output[buckets[(a[i] / exp) % 10] - 1] = a[i];
buckets[(a[i] / exp) % 10]--;
}
// 将排序好的数据赋值给a[]
for (i = 0; i < n; i++) {
a[i] = output[i];
}
}
// 基数排序
//
// 参数说明
//
// a -- 数组
// n -- 数组长度
//
void radix_sort(int a[], int n) {
int exp;
// 指数。当对数组按各位进行排序时,exp=1;按十位进行排序时,exp=10。
int max = get_max(a, n);// 数组a中的最大值
// 从个位开始,对数组a按指数进行排序
for (exp = 1; max / exp > 0; exp *= 10) {
count_sort(a, n, exp);
}
}
int main() {
int arr[] = { 53,3,3,542,3,616,748,14,214,154,63,616 };
radix_sort(arr, 12);
for (int i = 0; i < 12; i++)
printf("%d\t", arr[i]);
return 0;
}
堆排序
#include<stdio.h>
#define size 10
void Swap(int* num, int i, int j) {
int temp;
temp = num[i];
num[i] = num[j];
num[j] = temp;
}
// 最大堆调整
void Heapify(int* num, int len, int k) {
if (k < len) {
int root = k; // 根结点
int lchild = 2 * k + 1; // 左孩子结点
int rchild = 2 * k + 2; // 右孩子结点
// 查找左右孩子结点中的最大结点
if (lchild < len && num[root] < num[lchild])
root = lchild;
if (rchild < len && num[root] < num[rchild])
root = rchild;
// 交换最大结点到根结点
if (root != k) {
Swap(num, root, k);
// 每次交换都可能影响到对应孩子结点子树的顺序
// 所以需要对交换后的孩子结点子树进行最大堆调整
Heapify(num, len, root);
}
}
}
// 创建最大堆
void CreateHeap(int* num, int len) {
int i;
// 最后一个结点下标
int last = len - 1;
// 最后一个结点的父结点下标
int parent = (last - 1) / 2;
// 从最后一个结点的父结点到根结点,依次进行最大堆调整
for (i = parent; i >= 0; i--)
Heapify(num, len, i);
}
// 堆排序
void HeapSort(int* num, int len) {
// 创建最大堆并进行最大堆调整
CreateHeap(num, len);
printf("最大堆调整!\n");
int i;
// 依次取出根结点(最大值)
for (i = len - 1; i >= 0; i--) {
// 将最大堆的根结点(最大值)换到最后一个结点
Swap(num, i, 0);
// 交换后二叉树的根结点发生了改变,故还需对根结点做最大堆调整(已交换的末尾结点不参与调整)
// 而此时根结点小于所有父结点,因而在调整时只需考虑最大孩子的分支即可
Heapify(num, i, 0);
}
}
int main() {
int i, num[size] = { 8, 4, 3, 1, 6, 9, 5, 7, 2, 0 };
HeapSort(num, size);
for (i = 0; i < size; i++)
printf("%d\t", num[i]);
return 0;
}
希尔排序
#include<stdio.h>
int array[] = { 1,2545,25,26,2,1,1323,4562,7855,3822,3656,236,263,27,37,283,377,2542,23,232,32,138,345 };
int lenght = sizeof(array) / sizeof(array[0]);
int main() {
int jmp = lenght / 2; //找出基准数据
while (jmp != 0) { //判断数组是否为空
for (int i = jmp; i < lenght; i++) {
int tmp = array[i];
int j = i - jmp;
while (j >= 0 && tmp < array[j]) {
array[j + jmp] = array[j];
j = j - jmp;
}
array[jmp + j] = tmp;
}
jmp = jmp / 2;
}
for (int i = 0; i < lenght; i++) {
printf("%d\t", array[i]);
}
}
桶排序
#include <stdio.h>
#include <stdlib.h>
typedef struct node {
int num; //数据域
struct node* next; //指针域
}KeyNode;
void bucket_sort(int a[], int size, int bucket_size) {
int i, j; //数组,数组长度,桶的大小
//定义动态的指针数组
KeyNode** bucket_num = (KeyNode**)malloc(bucket_size * sizeof(KeyNode*));
for (i = 0; i < bucket_size; i++)
{
bucket_num[i] = (KeyNode*)malloc(sizeof(KeyNode));//为每个链表定义头结点
bucket_num[i]->num = 0;
bucket_num[i]->next = NULL; //指针变量初始化为空
}
for (j = 0; j < size; j++) //准备插入
{
KeyNode* node = (KeyNode*)malloc(sizeof(KeyNode));//定义一个节点
node->num = a[j]; //数据域存数据
node->next = NULL; //指向空
int index = a[j] / 100; //映射函数 计算桶号
KeyNode* p = bucket_num[index];//p指向链表的头
//链表结构的插入排序
while (p->next != NULL && p->next->num <= node->num)
{
p = p->next; //1.链表为空,p->next==NULL,进入不了循环
} //2.链表不为空,因为链表从无开始按顺序插入,数据为有序的,
//可以找到 前一个节点 <= node <=后一个节点
//节点插入链表
node->next = p->next;
p->next = node;
(bucket_num[index]->num)++; //记录一下该链表中有几个有效节点
}
//打印结果
KeyNode* k = NULL; //定义一个空的结构体指针用于储存输出结果
for (i = 0; i < bucket_size; i++)
{
//for(k = bucket_num[i]->next;k!=NULL;k=k->next)//通过最后一个指针指向空
k = bucket_num[i]->next;
for (int m = 0; m < bucket_num[i]->num; m++) //通过头指针记录节点数
{
printf("%d ", k->num);
k = k->next;
}
printf("\n");
}
}
int main(){
int a[20];
for (int i = 0; i < 20; i++){
a[i] = rand() % 1000; //给数组赋随机数
printf("%d ", a[i]);
}
puts("");
puts("");
int size = sizeof(a) / sizeof(int); //计算数组长度
bucket_sort(a, size, 10);//数组名,数组长度,桶的个数
}
计数排序
#include<stdio.h>
#include<string.h>
#include<stdlib.h>
//计数排序(优化后)
void CountSort(int* arr, int n){
//找到序列中的最大值和最小值
int max = arr[0];
int min = arr[0];
for (int i = 0; i < n; i++){
if (arr[i] > max)
max = arr[i];
if (arr[i] < min)
min = arr[i];
}
int range = max - min + 1;//开辟空间的数量
int* countArr = (int*)malloc(sizeof(int) * range);//开辟空间
//初始化数组全部为0
memset(countArr, 0, sizeof(int) * range);
//开始计数
for (int i = 0; i < n; i++)
countArr[arr[i] - min]++;
//开始排序
int j = 0;
for (int i = 0; i < range; i++){
while (countArr[i]--){
arr[j] = i + min;
j++;
}
}
free(countArr);
}
void Print(int* arr, int n){
for (int i = 0; i < n; i++)
printf("%d ", arr[i]);
}
void test(){
int arr[] = { -5,8,5,4,6,8,9,7,2,3,4,5 };
int n = sizeof(arr) / sizeof(arr[0]);
CountSort(arr, n);
Print(arr, n);
}
int main(){
test();
return 0;
}
你最爱的题库😍(【C语言】机试100题及代码答案(上)_c语言编程100题及答案-CSDN博客)
- 编写一个程序,从用户输入中读取整数并计算它们的和。
- 实现一个简单的计算器程序,可以执行加法、减法、乘法和除法运算。
- 编写一个函数,计算给定数组的平均值。
- 创建一个结构体表示学生,包含姓名、年龄和成绩。编写函数,输出成绩最高的学生信息。
- 实现一个递归函数,计算斐波那契数列的第 n 项。
- 编写一个程序,使用指针交换两个整数的值。
- 创建一个链表,并实现插入、删除和打印链表的函数。
- 编写一个程序,接受用户输入的字符串,然后反转字符串并输出。
- 实现冒泡排序算法对整数数组进行排序。
- 创建一个简单的文件读写程序,将数据写入文件并从文件中读取。
- 编写一个递归函数,计算给定数字的阶乘。
- 实现一个简单的栈数据结构,并编写函数进行入栈和出栈操作。
- 创建一个二维数组表示矩阵,编写函数计算矩阵的转置。
- 编写一个程序,接受用户输入的日期,然后判断这一天是星期几。
- 实现快速排序算法对整数数组进行排序。
- 创建一个简单的命令行日历程序,显示当前月份的日历。
- 编写函数,查找给定数组中的最小值和最大值。
- 实现一个简单的队列数据结构,并编写函数进行入队和出队操作。
- 编写一个递归函数,计算给定底数和指数的幂。
- 创建一个简单的登录系统,验证用户输入的用户名和密码。
- 实现选择排序算法对整数数组进行排序。
- 编写程序,将一个字符串中的所有元音字母替换为大写字母。
- 创建一个简单的电话簿程序,实现添加、删除和查找联系人的功能。
- 编写一个递归函数,计算给定数字的斩尾求和。
- 实现一个简单的二叉搜索树,并编写插入和查找函数。
- 创建一个简单的计算器程序,支持基本的加减乘除运算。
- 编写一个程序,计算并输出用户输入的整数的平方根。
- 实现插入排序算法对整数数组进行排序。
- 创建一个简单的文本编辑器程序,支持基本的文本编辑操作。
- 编写函数,判断给定字符串是否是回文字符串。
- 实现一个简单的图数据结构,并编写深度优先搜索(DFS)和广度优先搜索(BFS)算法。
- 创建一个简单的邮件系统,实现发送和接收邮件的功能。
- 编写程序,读取文件中的整数并计算它们的平均值。
- 实现基本的加密算法,例如凯撒密码或简单的替换密码。
- 创建一个简单的电子商务系统,实现购物车和结账功能。
- 编写程序,接受用户输入的字符串并检查是否为有效的IP地址。
- 实现归并排序算法对整数数组进行排序。
- 创建一个简单的图形界面程序,显示一个按钮并响应按钮点击事件。
- 编写函数,将给定的十进制整数转换为二进制表示。
- 实现一个简单的网络爬虫,抓取网页上的文本信息。
- 创建一个简单的日记本程序,实现添加和查看日记的功能。
- 编写程序,读取CSV文件并输出文件中每行的平均值。
- 实现基本的图像处理算法,例如灰度化或图像模糊。
- 创建一个简单的游戏,例如猜数字或井字棋。
- 编写函数,将给定的字符串转换为小写或大写。
- 实现基本的数据库操作,例如插入、查询和更新记录。
- 创建一个简单的天气预报程序,从网络获取天气信息并显示。
- 编写程序,计算用户输入的方程的根。
- 实现基本的文件压缩算法,例如哈夫曼编码。
- 创建一个简单的聊天程序,实现消息的发送和接收功能