C语言笔记
文章目录
序言:了解计算机原理
如果希望计算机做某些事,就必须为 其提供特殊的指令列表(程序),确切地告诉计算机要做的事以及如何做。
你必须用计算机能直接明白的语言(机器语言)创建程序。
计算机要完成诸如两数相加这样简单的事,就得分成类 似以下几个步骤。
1.从内存位置2000上把一个数字拷贝到寄存器1。(计算机的内存位置可以理解为C语言中的地址)
2.从内存位置2004上把另一个数字拷贝到寄存器2。
3.把寄存器2中的内容与寄存器1中的内容相加,把结果储存在寄存器1中。
4.把寄存器1中的内容拷贝到内存位置2008。
编译器
编译器是把高级语言程序翻译成计算机能理 解的机器语言指令集的程序。(比如说dev-c++)
编译器是把源代码转换成可执行代码的程序。可执行代码 是用计算机的机器语言表示的代码。
C编译器还将源代码与C库(库中包含大量的 标准函数供用户使用,如printf()
和scanf()
)的代码合并成最终的程序(链接器的作用是,把你编写的目标代码、系统的标准启动代码和库代码 这 3 部分合并成一个文件,即可执行文件)。其结果是,生成一个用户可以运行的可执行文件,其中包含着计算机能理解的代码。
编译环境
1.新手教程
C语言比其他语言 更依赖库,因此需要一个标准库。
//输入四个数字拆分相加
#include <stdio.h>
int main()
{
int s,a,b,c,d,sum;
printf("请输入一个四位数:");
scanf("%d",&s);
a=s/1000;
b=(s/100)%10;
c=(s/10)%10;
d=s%10;
sum=a+b+c+d;
printf("这四位数的拆分相加得到:%d",sum);
}
每个都需要用大括号包含起来,其中与python中相似的print()
是printf()
但是在c语言里面要有#include <stdio.h>
才可以。
1.1 转义字符
以下是常见的转义字符列表:
- \n - 换行
- \t - 制表符
- \r - 回车
- \b - 退格
- \f - 换页
- \ - 反斜杠
- ’ - 单引号
- " - 双引号
这些转义字符在编程中经常用于表示特殊的文本格式或字符。
1.2 字符类型表达式
char 字符数据类型 C语言表示用字符用 ' '(单引号),%c所对应的是打印字符格式的数据。
short 短整型数据类型 也可以写成 short int,短整形所占的存储空间大小可能比int小,常用于较小的值节省空间。
int 整形 %d所对应的是打印整形十进制(0~9)的数据
long 长整形 也可以写成 long int, 长整型所占的存储空间大小可能比int多,常用于较大的数值值场合。
long long 更长的整形 也可以写成 long long int,存储空间可能比long多,适用于更大的数值场合。
float 单精度浮点型 建议%f所对应的是打印单精度浮点数格式的数据,打印后面的小数点位数少点。
double 双精度浮点型 建议%lf所对印的是打印双精度浮点数格式的数据,打印后面的小数点位数多点。
1.3宏(#define)
宏是一种预处理指令,它允许您在编译之前替换源代码中的文本。宏通常用于定义常量、函数或其他代码块,以提高代码的可读性、可维护性和可重用性。
语法:
#define 宏名 替换文本
以下是运用宏的事例:
#include<stdio.h>
#define N 3
//创建了一个名为N的宏,它将被预处理器替换为3。
//这意味着在程序中每当预处理器遇到N时,它会用3来替换。
int main()
{
print("%d",N);
return 0;
}
1.4输出
格式说明符可用于输出不同类型的数据:
整型:
特殊字符 | 输出 |
---|---|
%o | 八进制整数 |
%x | 十六进制整数(小写) |
%X | 十六进制整数(大写) |
%u | 无符号十进制整数 |
%i | 有符号十进制整数 |
浮点型:
特殊字符 | 输出 |
---|---|
%E | 科学计数法(大写) |
%g | 通用格式(根据大小自动选择 %e 或 %f) |
%G | 通用格式(根据大小自动选择 %E 或 %F) |
%e | 科学计数法(小写) |
字符和字符串:
特殊字符 | 输出 |
---|---|
%s | 字符串 |
%c | 单个字符 |
指针:
特殊字符 | 输出 |
---|---|
%p | 指针地址(十六进制) |
其他:
特殊字符 | 输出 |
---|---|
%% | 打印一个百分号(%) |
1.5stringh函数库
<string.h>
头文件包含用于字符串操作的函数,包括:
strcpy
, strncpy
, strcat
, strncat
: 字符串复制和连接
strcmp
, strncmp
, strcoll
, strxfrm
: 字符串比较
strlen
, strnlen
: 字符串长度
strchr
, strrchr
, strstr
, strtok
: 字符串搜索
memset
, memcpy
, memmove
: 内存操作
strerror
: 获取错误消息
1.6静态变量
1.6.2 静态变量static
的使用1:
#include <stdio.h>
void increment()
{
static int count = 0; // 静态变量
count++;
printf("Count: %d\n", count);
}
int main()
{
increment(); // 输出 Count: 1
increment(); // 输出 Count: 2
increment(); // 输出 Count: 3
return 0;
}
在上述例子中,count是一个静态变量,每次调用increment函数时,静态变量count的值会被保留,并在下一次调用时继续使用。这意味着count变量在函数调用之间保持了状态,而不是像自动变量一样在每次函数调用结束时被销毁。
在上述例子中,count是一个静态变量,每次调用increment函数时,静态变量count的值会被保留,并在下一次调用时继续使用。这意味着count变量在函数调用之间保持了状态,而不是像自动变量一样在每次函数调用结束时被销毁。
1.6.2静态函数的使用2:
#include <stdio.h>
static void helper()
{
printf("This is a static helper function.\n");
}
int main()
{
helper(); // 可以在同一个源文件中直接调用静态函数
return 0;
}
在上述例子中,helper函数被声明为静态函数,它只能在同一个源文件中直接调用。这样可以限制函数的作用域,避免与其他源文件中的函数发生命名冲突,并提高程序的安全性。
在上述例子中,helper函数被声明为静态函数,它只能在同一个源文件中直接调用。这样可以限制函数的作用域,避免与其他源文件中的函数发生命名冲突,并提高程序的安全性。
1.6.3 限定符的使用:
// 文件1.c
static int count = 0; // 限定在当前文件范围内
// 文件2.c
extern int count; // 引用文件1.c中的count变量
int main()
{
count = 10; // 可以访问文件1.c中的count变量
return 0;
}
在上述例子中,通过在文件1.c中使用static关键字将变量count限定在当前文件范围内,其他文件无法访问该变量。然后,在文件2.c中使用extern关键字声明了一个同名的变量count,这样就可以在文件2.c中访问并使用文件1.c中定义的静态变量count。
在上述例子中,通过在文件1.c中使用static关键字将变量count限定在当前文件范围内,其他文件无法访问该变量。然后,在文件2.c中使用extern关键字声明了一个同名的变量count,这样就可以在文件2.c中访问并使用文件1.c中定义的静态变量count。
这些例子展示了static关键字的不同用法,包括静态变量的状态保持、静态函数的作用域限制以及限定符的使用。根据具体的需求,static关键字可以帮助我们编写更灵活和安全的代码。
1.7 外部函数
在C语言中,extern
关键字用于声明一个外部函数,表示该函数在当前文件中没有定义,但是在其他文件中有定义。
通过使用extern关键字声明一个外部函数,我们可以在当前文件中使用该函数,而不需要在当前文件中重新定义它。这通常用于在多个源文件之间共享函数的定义。
下面是一个使用extern关键字声明外部函数的示例:
假设我们有两个源文件:file1.c和file2.c。
file1.c:
#include <stdio.h>
extern void foo(); // 声明外部函数
int main() {
foo(); // 在当前文件中使用外部函数
return 0;
}
file2.c:
#include <stdio.h>
void foo() {
printf("This is an external function.\n");
}
在上述示例中,file1.c中使用了extern关键字声明了一个外部函数foo,它在当前文件中没有定义。然后,在file2.c中定义了函数foo的实际实现。
当我们编译并运行file1.c时,程序会在运行时查找定义了foo函数的外部文件(在这里是file2.c),并正确地执行该函数。
使用extern关键字可以帮助我们在不同的源文件中共享函数定义,提高代码的模块化和可维护性。
2.碎片
2.1 sizeof(字节数)
sizeof
运算符返回其操作数(变量、数据类型或表达式)在内存中占用的字节数。
sizeof(操作数)
2.2随机数(引入include<stdlib.h>)
根据下表
int a=rand()%10; //注意10会被整除,所以不包括10
int a=66+rand()%23;//在[66,88]里面取随机数
int a=m+rand()%(n-m+1);//在[m,n]
以下代码是,随机取随机数。
//随机数
#include <stdio.h>
#include <stdlib.h>//关于随机数
#include <time.h>
int main()
{
int a;
srand((unsigned int)time(NULL));//我们在使用rand和srand时,主要使用的就是这一种初始化方法
//如果非要取随机数那么一定要加上`srand((unsigned int )time(NULL))`
a=rand();//如果只用它的话,不用上面的,只会产生一个固定的数
printf("%d",a);
return 0;
}
2.3 unsigned
unsigned 是一个类型限定符,用于声明无符号数据类型。无符号数据类型只能表示非负值。
unsigned int x; // 无符号整型
unsigned char c; // 无符号字符
2.4 const
它表示一个变量、指针或其他实体的常量性。主要的作用是防止以外修改。
const 的一些具体用法示例:
常量变量:
const int MY_CONSTANT = 42;
以此举个例子:区分const int
和int
的区别
const int
常量整数的注意事项:
使用 const int
常量整数时需要注意以下几点:
- 必须在编译时初始化:
const
变量必须在编译时初始化,这意味着您不能在运行时为它们分配值。 - 不能重新赋值:
const
变量一旦被初始化,其值就不能被重新赋值。 - 可以隐式转换为非 const 类型:
const
变量可以隐式转换为非const
类型,这可能会导致意外的行为。
常量字符串:
const char* MY_STRING = "Hello, world!";
常量指针:
const int* MY_POINTER = &my_variable;
常量引用:
const int& MY_REFERENCE = my_variable;
常量类成员变量:
class MyClass {
public:
const int MY_MEMBER_VARIABLE = 42;
};
明智地使用 const 可以显着提高您的代码的质量和健壮性。
3. math.h的应用
3.1 fabs(绝对值)
对于以下是有做改造的,因为用了内置函数就是#include <math.h>
,使返回绝对值fabs
有效,就简便了很多。
还有就是,对于整数和小数这方面有极严的要求,即float和int不要写错了也不要根据简便随便乱写,要根据逻辑写。
//返回整数和小数的绝对值
#include <stdio.h>
#include <math.h>
int main()
{
float number;
printf("请输入数值:");
scanf("%f",&number);
printf("%f\n",fabs(number));
return 0;
}
3.2 pow(幂函数)
pow 函数(全称 power,意为幂)是一个数学函数,用于计算一个数的幂。它的原型如下:
double pow(double base, double exponent);
其中:
base 是要计算幂的底数。
exponent 是幂的指数。
pow 函数返回底数乘以自身 exponent 次的结果。例如:
pow(2.0, 3.0) == 8.0 // 2 的 3 次方等于 8
pow(-3.0, 2.0) == 9.0 // -3 的 2 次方等于 9
3.3 round(四舍五入)
round 函数是一个数学函数,用于对数字进行四舍五入。它的原型如下:
double round(double number);
其中:
number 是要四舍五入的数字。
round 函数返回四舍五入到最接近整数的数字。例如:
round(2.5) == 3.0 // 四舍五入到最接近的整数 3
round(-1.5) == -1.0 // 四舍五入到最接近的整数 -1
4.文本文件file
4.1打开文件
FILE *file = fopen("filename.txt", "r");
if (file == NULL)
{
printf("无法打开文件\n");
return 1;
}
fopen() 函数以只读模式打开文件,并返回一个文件指针。
如果文件打开失败,fopen() 会返回 NULL,并且 perror() 函数会打印错误消息。
4.2 写入文本文件
4.2.1自发写入
4.2.1.1 fprintf
什么都可以
// 将数据写入文件
fprintf(fp, "Hello, world!\n");
4.2.1.2 fputs
将一个字符串写入一个流(文件或标准输出)
// 使用 fputs 写入一个字符串
fputs("Hello, world!\n", fp);
4.2.1.3 fwrite
将一个二进制数据块写入一个流。
// 使用 fwrite 写入一个二进制数据块
fwrite(&buffer, sizeof(buffer), 1, fp);
4.2.1.4 fputc
将一个字符写入一个流。
// 使用 fputc 写入一个字符
fputc('c', fp);
4.2.2 人机互动式写入
4.2.2.1 getchar
// 逐个字符地读取文本
char c;
while ((c = getchar()) != EOF)
{
// 将字符写入文件
putchar(c);
}
4.2.2.2 fgets
char buffer[1024];
// 逐行读取文本
while (fgets(buffer, sizeof(buffer), stdin) != NULL)
{
// 将行写入文件
fputs(buffer, fp);
}
4.3 读取的两种形式
4.3.1 fgets
fgets 函数在读取换行符或指定字符数(由第二个参数决定)之后会停止,因此更为安全。
fgets() 函数逐行读取文件,并将内容存储在缓冲区 buffer 中。
sizeof(buffer) 指定缓冲区的大小。
当 fgets() 返回 NULL 时,表示已到达文件末尾。
char buffer[100];
while (fgets(buffer, sizeof(buffer), file) != NULL)
{
printf("%s", buffer);
}
4.3.2 fscanf
fscanf() 函数从流(例如文件)中读取格式化的数据并将其存储在指定的变量中。
使用 fscanf() 函数从文件中读取数据。可以使用以下格式说明符读取文本:
%s
:读取一个字符串(直到遇到空白字符)。%c
:读取单个字符。%[^\ ]
:读取一行(直到遇到换行符)。
// 逐行读取文件
char line[100];
while (fscanf(file, "%[^\n]\n", line) != EOF)
{
printf("%s\n", line);//一行一行读取
}
4.3.3 fgetc
// 逐个字符读取文件
char c;
while ((c = fgetc(fp)) != EOF)
{
// 输出字符
putchar(c);
}
4.4 关闭文件
fclose(fp);
fclose() 函数关闭文件,释放与文件关联的资源。
5.指针
5.1指针定义
指针是一种数据类型,它存储另一个变量的地址。
指针的语法如下:
<data_type> *<variable_name>;
其中:
<data_type> 是要指向的数据类型。
<variable_name> 是指针变量的名称。
例如,以下代码声明了一个指向整数的指针:
int *ptr;
要让指针指向一个变量,可以使用取地址运算符 &:
int var = 10;
ptr = &var;
现在,ptr 指向变量 var,并且可以通过指针访问 var 的值:
*ptr = 20; // 通过指针修改 var 的值
printf("%d\n", var); // 输出 var 的值,现在为 20
5.2 指针基本类型
int *p1; //p1是指向int型变量的指针变量
float *p2; //p2是指向float型变量的指针变量
double *p3; //p3是指向double型变量的指针变量
char *p4; //p4是指向char型变量的指针变量
5.3 注意事项
- 只要是数组,不带的数组名指的都是数组首位的地址。
- 只要是数组名或者地址前面有一个*,那么指的就是数组的首位的内容,或者地址的内容
- 只要前面加一个&就是指的那个的地址
- 地址是可以加减的。注意:数组名后面直接加减就是可以的
6.字符串处理函数
6.1 [得到]
6.1.1 scanf
scanf函数的语法:
int scanf(const char *format, ...);
参数:
format:一个控制字符串,指定要读取数据的格式。
…:一个可变数量的参数,指向要存储读取数据的变量的地址。
返回值:
成功读取数据的项数。
遇到文件结束符 (EOF) 时返回 EOF。
出错时返回 0。
用法:
scanf 函数使用 format 字符串中指定的格式从标准输入读取数据。format 字符串包含格式说明符,每个格式说明符指定要读取数据的类型和宽度。
要存储读取数据的变量的地址作为 scanf 函数的参数传递。
例子:
#include <stdio.h>
int main() {
int age;
float weight;
printf("Enter your age and weight: ");
scanf("%d %f", &age, &weight);
printf("Your age is %d and your weight is %.2f\
", age, weight);
return 0;
}
用户输入:
25 75.5
输出:
Your age is 25 and your weight is 75.50
6.1.2 gets
gets函数的语法:
char *gets(char *str);
参数:
str:一个字符数组,用于存储读取的文本。
返回值:
成功读取文本时返回 str。
遇到文件结束符 (EOF) 时返回 NULL。
出错时返回 NULL。
用法:
gets 函数从标准输入读取一行文本,直到遇到换行符 (’
') 或文件结束符 (EOF)。读取的文本存储在 str 字符数组中。
例如,以下代码使用 gets 函数从标准输入读取一行文本:
#include <stdio.h>
int main() {
char str[100];
printf("Enter a line of text: ");
gets(str);
printf("You entered: %s", str);
return 0;
}
用户输入:
Hello, world!
输出:
You entered: Hello, world!
6.1.3 getchar
getchar函数的语法:
int getchar(void);
参数:
无
返回值:
读取的字符(作为 int 值)。
如果遇到文件结束符 (EOF),则返回 EOF。
用法:
getchar 函数从标准输入读取单个字符并返回其 ASCII 码值。它会一直阻塞,直到用户输入一个字符。
例子:
#include <stdio.h>
int main() {
char ch;
printf("Enter a character: ");
ch = getchar();
printf("You entered the character '%c' (ASCII code: %d)\n", ch, ch);
return 0;
}
用户输入:
a
输出:
You entered the character 'a' (ASCII code: 97)
6.1.4 fgets()
fgets函数的语法:
char *fgets(char *str, int num, FILE *stream);
参数:
**str:**指向要存储读取行的字符数组的指针。
**num:**要读取的最大字符数(包括换行符)。
**stream:**要读取的文件流或标准输入流(stdin)。
返回值:
成功读取一行时返回指向 str 的指针。
如果遇到文件结束符 (EOF) 或读取错误,则返回 NULL。
用法:
fgets 函数从指定的流中**读取一行(以换行符结尾)**并将其存储在 str 指向的字符数组中。它会一直阻塞,直到读取到换行符或达到最大字符数(num)。
例子:
#include <stdio.h>
int main() {
char str[100];
printf("Enter a line of text: ");
fgets(str, 100, stdin);
printf("You entered: %s", str);
return 0;
}
用户输入:
Hello, world!
输出:
You entered: Hello, world!
6.2[输出]
6.2.1 puts
puts函数的语法:
int puts(const char *str);
参数:
str:要输出的字符串。
返回值:
成功时返回 0。
出错时返回 EOF(-1)。
用法:
puts 函数将指定的字符串(包括换行符)写入标准输出流。它可以用于打印字符串或创建简单的文本输出。
例子:
#include <stdio.h>
int main()
{
char str[] = "china!";
puts(str);
return 0;
}
输出:
china!
6.2.2 putchar
putchar函数的语法:
int putchar(int ch);
参数:
ch:要输出的字符。
返回值:
成功时返回输出的字符。
出错时返回 EOF(-1)。
用法:
putchar 函数将指定的字符写入标准输出流。它可以用于打印单个字符或创建简单的文本输出。
例子:
#include <stdio.h>
int main() {
putchar('A');
return 0;
}
输出:
A
6.3[复制]
6.3.1 strcpy
strcpy函数的语法:
char *strcpy(char *dest, const char *src);
参数:
dest:目标字符串。
src:源字符串。
返回值:
strcpy 函数返回目标字符串 dest。
用法:
strcpy 函数将源字符串 src 中的内容复制到目标字符串 dest 中。复制会持续到源字符串中的空字符(‘\0’)。
例子:
#include <string.h>
int main() {
char dest[10];
const char *src = "hello";
strcpy(dest, src);
printf("dest: %s\n", dest);
return 0;
}
输出:
dest: hello
6.3.2 strncpy
strncpy函数的语法:
char *strncpy(char *dest, const char *src, size_t n);
参数:
dest:目标字符串。
src:源字符串。
n:要复制的字符数。
返回值:
strncpy 函数返回目标字符串 dest。
用法:
strncpy 函数将源字符串 src 中的前 n 个字符复制到目标字符串 dest 中。复制会持续到源字符串中的空字符(‘\0’)或复制了 n 个字符为止。
例子:
#include <stdio.h>
#include <string.h>
int main() {
char dest[20];
char src[] = "Hello, world!";
strncpy(dest, src, 5);
dest[5] = '\0'; // 手动添加 NULL 终止符,因为 strncpy 不保证自动添加
printf("%s", dest); // 输出:Hello
return 0;
}
输出:
Hello
6.4 [比较]
6.4.1 strcmp
strcmp函数的语法:
int strcmp(const char *s1, const char *s2);
参数:
s1:要比较的第一个字符串。
s2:要比较的第二个字符串。
返回值:
如果 s1 小于 s2,则返回一个负值。
如果 s1 等于 s2,则返回零。
如果 s1 大于 s2,则返回一个正值。
用法:
strcmp 函数逐个字符地比较两个字符串,直到遇到空字符(‘\t’)。如果在比较过程中遇到空字符,则认为较短的字符串小于较长的字符串。
例子:
#include <string.h>
int main() {
char s1[] = "hello";
char s2[] = "world";
int result = strcmp(s1, s2);
if (result < 0) {
printf("s1 is less than s2.\\
");
} else if (result == 0) {
printf("s1 is equal to s2.\\
");
} else {
printf("s1 is greater than s2.\\
");
}
return 0;
}
输出:
s1 is less than s2.
6.4.2 strcroll
strcroll函数的语法:
int strcoll(const char *s1, const char *s2);
参数:
s1:要比较的第一个字符串。
s2:要比较的第二个字符串。
返回值:
如果 s1 小于 s2,则返回一个负值。
如果 s1 等于 s2,则返回零。
用法:
strcoll 函数使用给定的区域设置(locale)对两个字符串进行词法比较。区域设置会影响字符串的排序顺序,例如字母的大小写顺序、特殊字符的排序方式以及数字的排序方式。
例如:
#include <string.h>
int main() {
char s1[] = "hello";
char s2[] = "world";
int result = strcoll(s1, s2);
if (result < 0) {
printf("s1 is less than s2.\n");
} else if (result == 0) {
printf("s1 is equal to s2.\n");
} else {
printf("s1 is greater than s2.\n");
}
return 0;
}
输出:
s1 is less than s2.
例如,以下代码使用 strcoll_l 函数比较字符串 “Hello” 和 “world”,并使用 C 语言的默认区域设置:
#include <stdio.h>
#include <locale.h>
#include <string.h>
int main() {
// 获取 C 语言的默认区域设置
locale_t locale = newlocale(LC_ALL_MASK, "C", NULL);
int result = strcoll_l("Hello", "world", locale);
if (result < 0) {
printf("The first string is less than the second string.\
");
} else if (result == 0) {
printf("The strings are equal.\
");
} else {
printf("The first string is greater than the second string.\
");
}
freelocale(locale);
return 0;
}
输出:
The first string is less than the second string.
6.5 [连接]
6.5.1 strcat
strcat函数的语法:
char *strcat(char *dest, const char *src);
参数:
dest:要附加源字符串的目标字符串。
src:要附加到目标字符串的源字符串。
返回值:
返回附加后的目标字符串。
用法:
strcat 函数将源字符串 src 的内容追加到目标字符串 dest 的末尾。如果目标字符串 dest 没有足够的空间来容纳附加的字符串,则函数的行为是未定义的。
例题:
#include <stdio.h>
#include <string.h>
int main() {
char str[20] = "Hello, ";
char str1[] = "world!";
strcat(str, str1);
printf("%s", str);
return 0;
}
输出:
Hello, world!
想要更安全的连接两个字符串的话,那就用strncat。
6.5.2 strncat
strncat函数的语法:
char *strncat(char *dest, const char *src, size_t n);
参数:
dest:要附加源字符串的目标字符串。
src:要附加到目标字符串的源字符串。
n:要附加的最大字符数。
返回值:
返回附加后的目标字符串。
用法:
strncat 函数将源字符串 src 的内容追加到目标字符串 dest 的末尾,最多追加 n 个字符。如果源字符串 src 的长度小于 n,则函数将附加源字符串的全部内容。如果源字符串 src 的长度大于 n,则函数将只追加前 n 个字符。
例子:
#include <stdio.h>
#include <string.h>
int main() {
char str[20] = "ndcqodwiq!"; // 20 是足够容纳两个字符串的长度
char str1[] = "aaa!";
strncat(str, str1, sizeof(str) - strlen(str) - 1); // 使用strncat来连接字符串
printf("%s", str);
return 0;
}
输出:
ndcqodwiq!aaa!
6.6 [长度]
6.6.1 strlen
strlen函数的语法:
size_t strlen(const char *str);
参数:
str:要计算长度的字符串。
返回值:
返回字符串 str 中字符的数量(不包括终止空字符 ‘\0’)。
用法:
strlen 函数通过遍历字符串并计算遇到的字符数来计算字符串的长度。它会在遇到第一个空字符(‘\0’)时停止遍历。
例子:
#include <stdio.h>
#include <string.h>
int main() {
char str[20] = "Hello!";
printf("%d", strlen(str));
return 0;
}
输出:
6
6.6.2 strnlen
strnlen 函数计算一个字符串中前 n 个字符的长度,其中 n 是一个 size_t 类型的参数。如果字符串的长度小于 n,则 strnlen 返回字符串的实际长度。
strnlen 函数语法:
size_t strnlen(const char *s, size_t n);
参数:
s 是要计算长度的字符串。
n 是要计算的前 n 个字符的长度。
返回值:
strnlen 函数返回字符串 s 中前 n 个字符的长度。如果字符串的长度小于 n,则返回字符串的实际长度。
示例:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello World";
size_t len = strnlen(str, 6);
printf("The length of the first 6 characters of the string is: %zu\n", len);
return 0;
}
输出:
The length of the first 6 characters of the string is: 6
6.7转大小写
6.7.1 strlwr
strlwr函数的语法:
char *strlwr(char *str);
参数:
str:要转换大小写的字符串。
返回值:
返回转换后的小写字符串。
用法:
strlwr 函数通过遍历字符串并检查每个字符是否是大写字母来工作。如果遇到大写字母,函数会将其转换为相应的小写字母。
例子:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
printf("原始字符串: %s\n", str);
printf("转换为小写: %s\n", strlwr(str));
return 0;
}
输出:
hello, world!
6.7.2 strupr
strupr函数的语法:
char *strupr(char *str);
参数:
str:要转换大小写的字符串。
返回值:
返回转换后的大写字符串。
用法:
strupr 函数通过遍历字符串并检查每个字符是否是小写字母来工作。如果遇到小写字母,函数会将其转换为相应的大写字母。
例子:
#include <stdio.h>
#include <string.h>
int main() {
char str[] = "Hello, World!";
printf("原始字符串: %s\n", str);
printf("转换为小写: %s\n", strupr(str));
return 0;
}
输出:
HELLO, WORLD!
6.8 [拆分]
6.8.1 strtok
strtok 函数的语法:
char *strtok(char *str, const char *delim);
参数:
- str: 要解析的字符串。如果为
NULL
,则继续解析上次调用strtok
时停止的位置。 - delim: 用于分隔标记的字符或字符串。
返回值:
指向第一个标记的指针,或 NULL
如果没有更多标记。
用法:
strtok 函数通过遍历字符串并搜索分隔符字符来工作。当它找到一个分隔符时,它会在该分隔符处将字符串分割成两个令牌。第一个令牌是分隔符之前的子字符串,第二个令牌是分隔符之后的子字符串。
例子:
#include <stdio.h>
#include <string.h>
int main()
{
char str[] = "apple,banana,cherry,dog";
char *token;
token = strtok(str, ",");
while (token != NULL)
{
printf("%s ", token);
token = strtok(NULL, ",");
}
return 0;
}
输出:
apple banana cherry dog
9 跳跃表
9.1跳跃表定义
跳表(Skip List)是一种数据结构,它是一种可以在平均O(log n)的时间复杂度内进行搜索、插入和删除操作的数据结构,类似于平衡树,但实现起来更简单。跳表通过在有序链表的基础上增加多层索引来加快搜索的速度,最下面一层的链表包含了所有元素,而每一层链表都是前一层链表的子集。通过这种方式,跳表可以通过索引层次的跳跃来快速地定位元素。
每两个节点提取到上一级。跳表中的节点在不同层级之间是通过"跳跃"的方式连接的。每个节点都存在于最底层,然后根据一定的概率(通常是1/2)随机地向上提升到更高层级。
跳跃表是由多层链表组成的,每一层都是原始链表的一个子集。
链表就是struct定义,或者type struct那个,多个节点构成一个链表,链表里面的就是节点,而一个链表就是一层
最底层包含所有元素,且是一个有序的链表。每个高层链表都是前一层链表的子集,通过一个搜索指针实现连接。
相当于金字塔形状,最底层包含所有,而上一层就是下面一层的子集,这样就可以加快巡查的时间
每个元素在跳跃表中出现的层数(高度)是随机决定的,这使得跳跃表的高度可以被平均到 O(log n),从而保证了搜索、插入和删除等操作的时间复杂度为 O(log n)。
i ! / ( ( i − j ) ! ∗ j ! ) i!/((i-j)!*j!) i!/((i−j)!∗j!)
10 二维数组
二维数组的主要形式是:arr[m][n]
这里的m是指有多少个数组,n是指数组里面最多能储存多大的字符串
要是写arr[i]的话那就是指arr二维数组中的某一个数组。
11 返回值类型
C 语言中的函数可以返回多种类型,包括 void
、int
、float
、double
和 char
。还有一些更复杂的类型,比如结构体、指针等,甚至也可以返回自定义的类型。
函数的返回类型需要根据其具体的功能和所需返回的数据类型来选择。例如,如果函数需要返回整数类型的数据,就可以选择 int
作为返回类型;如果函数不需要返回任何值,可以选择 void
作为返回类型。
void不止可以不返回,还可以用指针表示,这样子更简单明了
c#include <stdio.h>
void func(int x, int y, int *p1, int *p2);
int main() {
int m, n, sum, product;
scanf("%d %d", &m, &n);
func(m, n, &sum, &product); // 传递m和n的值以及sum和product的地址
printf("%d + %d = %d\n", m, n, sum);
printf("%d * %d = %d\n", m, n, product);
return 0;
}
void func(int x, int y, int *p1, int *p2) {
*p1 = x + y;
*p2 = x * y;
}
在这个程序中,函数 func 被用来计算两个整数 x 和 y 的和与积,并将结果通过指针返回给调用者。
通过指针参数可以修改函数外部的变量值:在 C 语言中,函数的参数传递默认是按值传递的,即函数内对参数的修改不会影响到函数外的原变量。但通过传递指针参数,可以通过指针间接访问到原变量,从而修改原变量的值。这样就可以在函数内部计算出的结果直接影响到函数外部的变量,而无需通过返回值来实现。
12结构体
取别名和指针是不一样的。如果是别名的话如果要表示结构体的元素的话那就直接用. 如果是指针要指向结构体的元素的话那就只能用->
12.1别名
这里就是一个结构体,而DATE就是date的一个别名,就是结构体的别名叫DATE
struct date
{
int year;
int month;
int day;
}DATE;
12.2scanf要用指针
如果你要使用 scanf 从用户输入中读取结构体的值,则需要传递结构体变量的地址,这样可以修改结构体变量的值。
void input(STUDENT *s)
{
scanf("%d", &(s->num));
scanf("%s", s->name);
scanf(" %c", &(s->sex));
scanf("%d%d%d", &(s->birthday.year), &(s->birthday.month), &(s->birthday.day));
scanf("%f", &(s->score));
}
12.3读取用结构体变量
如果声明了一个名为 s 的结构体变量,那么可以使用 s.num 来访问结构体变量 s 的成员 num。这种写法表明 s 是一个结构体类型的变量,而不是指针,因此可以使用 . 运算符来访问其成员
void output(STUDENT s)
{
printf("学号:%d\t姓名:%s\t性别:%c\t出生日期:%d-%d-%d \t成绩:%.1f\n", s.num, s.name, s.sex, s.birthday.year, s.birthday.month, s.birthday.day, s.score);
}
12.4常见动态数组的函数
将在函数体里申请的数组的首地址和数组的长度带到函数体外
*s=( STUDENT *) malloc( (*n) * sizeof (STUDENT) );
这里就是指创了N个STUDENT的字节数,分配了这么大的内存空间
void createarr(STUDENT **s,int *n)
{
scanf("%d",n);
*s=( STUDENT *) malloc( (*n) * sizeof (STUDENT) );
if( *s == NULL)
{
printf("不能成功分配存储空间。\n");
exit(1);
}
}
13链式存储结构(动态链表)
13.1定义
在计算机中,除了用一组地址连续的存储单元存放数据,还可以用一组地址不连续的存储单元存储数据,这种存放数据的结构,称作链式存储结构。
链式存储结构是一种动态地进行存储分配的数据结构,优点是不需要事先确定最大长度,在插入或者删除元素时,也不会引起数据的大量移动;缺点是只能顺序访问链表中的元素,不能随机存取数据。
设有4个相同类型的数据A、B、C、D,这4个数据类型可以是数值型、字符串型或自定义的结构体类型,它们的顺序存储结构和链式存储结构的示意图如图 1 所示。
设计一个单链表的结点结构如下:
链式存储结构中,每个元素称为一个结点(节点),每个结点都可存储在内存中不同的位置,为了表示每个元素与后继元素之间的逻辑关系,以便构成一个结点链着一个结点的链式存储结构,每个结点都包含两个部分:第 1 部分是数据域,用来存储元素本身的数据信息,这里用data表示;第 2 部分是指针域,用来存储下一个结点的地址,这里用next表示。如此串连下去直到最后一个结点,最后一个结点称为“表尾”,该结点的指针域值为 0,指向内存中编号为零的地址(常用符号常量 NULL 表示,称为空地址),表尾不再有后继结点。
14.2举例
举例说明,输入3个整数,链式存放,定义链表的结点类型如下:
struct Node
{
int data;
struct Node *next;
} ;
typedef struct Node LNode;
typedef struct Node * LinkList;
data成员用于存放输入的整数,next成员用于存放下一个结点的地址。
如果结点的类型定义采用如下方法,系统编译能不能通过?
struct Node
{
int data;
struct Node next;
};
系统编译不能通过,结构体声明时不能包含本结构体类型成员,这是因为本结构体类型尚未定义结束,它所占用的内存单元的字节数尚未确定,因此系统无法为这样的结构体成员分配内存。结构体声明时,可以包含本结构体类型的指针成员。
/**********创建3个结点存放3个整数 **********/
/**********创建3个结点存放3个整数 **********/
LNode * p1, * p2, * p3;
p1=( LNode *)malloc(sizeof(LNode)); //申请动态内存
if(p1==NULL) //对动态内存分配是否成功进行检测
{
printf("不能成功分配存储空间。\n");
exit(1);
}
scanf("%d",&p1->data); //将输入的整数存放在结点的data域
/**********申请动态内存的同时并且检测**********/
if ( ( p2 = (LNode *)malloc(sizeof(LNode) ) ) == NULL )
{
printf("不能成功分配存储空间。\n");
exit(1);
}
scanf("%d",&p2->data); //将输入的整数存放在结点的data域
if( (p3 = (LNode t *)malloc(sizeof(LNode)) ) == NULL)
{
printf("不能成功分配存储空间。\n");
exit(1);
}
scanf("%d", &p3->data ); //将输入的整数存放在结点的data域
/**********将3个结点连接起来 **********/
p1->next = p2; //将p2链接在p1后
p2->next = p3; //将p3链接在p2后
p3->next = NULL; // p3后没有结点
结点链接后通过p1,能找到p2,通过p2,能找到p3。p2,p3的值不重要了,重要的是p1的值不能丢。改进上述操作,用循环语句来完成建立链表:
/**********循环建立3个结点的链表**********/
LNode * p1, * p2, *head;
if ( ( p1 = (LNode *)malloc(sizeof(LNode) ) ) == NULL ) //创建第1个结点
{
printf("不能成功分配存储空间。\n");
exit(1);
}
scanf("%d",&p1->data);
head = p1;
for(i=1;i<3;i++) //循环创建其余结点
{
if ( ( p2 = (LNode *)malloc(sizeof(LNode) ) ) == NULL )
{
printf("不能成功分配存储空间。\n");
exit(1);
}
scanf("%d",&p2->data);
p1->next = p2;
p1 = p2;
}
p2->next=NULL; //将链表最后一个结点指针域置为空值
}
如果希望将第1个结点的创建也在循环语句里进行,可以在第1个结点之前附设一个头结点,单链表有带头结点和不带头结点之分:
在链表的开始结点之前附加一个结点,并称它为头结点,头结点的数据成员不用来存放数据,那么会带来以下两个优点:
由于存放第一个数据元素结点的位置被存放在头结点的指针域中,所以在链表的第一个位置上的操作就和在表的其它位置上的操作一致,无需进行特殊处理;
无论链表是否为空,其头指针是指向头结点的非空指针,空表中头结点的指针域为空,因此空表和非空表的处理也就统一了。
LNode * p1, * p2, *head;
if ( ( head = (LNode *)malloc(sizeof(LNode) ) ) == NULL ) //创建头结点
{
printf("不能成功分配存储空间。\n");
exit(1);
}
p1 = head;
for(i=0; i<3; i++) //循环创建其余结点
{
if ( ( p2 = (LNode *)malloc(sizeof(LNode) ) ) == NULL )
{
printf("不能成功分配存储空间。\n");
exit(1);
}
scanf("%d",&p2->data);
p1->next = p2;
p1 = p2; //p1始终指向链表最后一个结点
}
p2->next=NULL; //将链表最后一个结点指针域置为空值
}
上述创建单链表的方法称为尾插法,将建立单链表的操作模块化,定义创建带头结点的单链表的函数:
void CreateTailList(LinkList L, int n)
{
LNode *p,* tail;
int i,x;
tail = L; //设置尾指针,方便插入
for(i=0; i<n; i++) //循环创建其余结点
{
if ( ( p = (LNode *)malloc(sizeof(LNode) ) ) == NULL )
{
printf("不能成功分配存储空间。\n");
exit(1);
}
scanf("%d",&p->data);
tail ->next = p;
tail = p; //p1始终指向链表最后一个结点
}
p->next=NULL; //将链表最后一个结点指针域置为空值
}
注意
本文只是学习C语言写的笔记,还有不是很完善,不定时会修改.