2024-07-23 第三章:数据和C
学习内容:
- float和double默认显示小数点后面的位数都是6,但是表示范围和精度都不相同;%d(%i)同等表示int类型数据。
浮点数VS定点数
2024-07-24 第四章:字符串的格式化输入/输出
- 控制字宽和输出格式
- scanf的本质是字符串匹配
- strlen库函数的实现代码
size_t strlen(const char* str){ const char* s=str; while(s){ s++; } return (size_t)(s-str); //只能减法,不可以加法 }
**字段宽度(Field Width)**是格式化输出函数(如printf
、scanf
、sprintf
等)中的一个重要概念。它用于指定输出或输入数据时占用的最小字符数。字段宽度可以通过格式说明符中的一个整数值来指定。
以下是一些常见的用法和示例:
-
整数输出:
- 例如,
printf("%5d", 123);
会输出123
,其中123
前面有2个空格,以确保总宽度为5个字符。
- 例如,
-
浮点数输出:
- 例如,
printf("%10.2f", 123.456);
会输出123.46
,其中123.46
前面有3个空格,总宽度为10个字符,小数点后有2位。
- 例如,
-
字符串输出:
- 例如,
printf("%10s", "Hello");
会输出Hello
,其中Hello
前面有5个空格,以确保总宽度为10个字符。
- 例如,
-
左对齐:
- 可以在字段宽度前加上负号(
-
)来实现左对齐。例如,printf("%-10s", "Hello");
会输出Hello
,其中Hello
后面有5个空格。
- 可以在字段宽度前加上负号(
-
零填充:
- 可以在字段宽度前加上零(
0
)来实现零填充。例如,printf("%05d", 123);
会输出00123
,其中123
前面有2个零。
- 可以在字段宽度前加上零(
-
输入字段宽度:
- 在输入函数(如
scanf
)中,字段宽度用于限制读取的字符数。例如,scanf("%5s", str);
会从输入中读取最多5个字符,并存储到字符串str
中。
- 在输入函数(如
总结一下,C语言中的字段宽度用于控制输出或输入数据时的格式和对齐方式,通过在格式说明符中指定一个整数值来实现。
2024-07-25 第五章:运算符、表达式和语句
- 基本语句表达没问题
2024-07-26 第六章:控制语句:循环
- for while switch-break do-while continue
go-to(闭关不建议使用)
2024-07-26 第七章:分支和跳转
- gechar putchar
- 三元运算符
2024-07-30 第八章:字符串的输入输出验证
#include <stdio.h>
#include <time.h>
int main() {
// 打开一个文件用于写入,没有的话就会创建
FILE* file = fopen("output.txt", "a+");
// 判断打开文件是否成功,失败直接return 1,结束程序
if (file == NULL) {
perror("Failed to open file");
return 1;
}
printf("文件创建/读取成功\n");
printf("开始写入\n");
// 获取开始时间
clock_t start_time = clock();
for (int i = 0; i < 10; i++) {
fprintf(file, "第%d次写入\n", i + 1);
}
// 关闭文件
fclose(file);
printf("文件关闭!\n");
// 获取结束时间
clock_t end_time = clock();
long long elapsed_ticks = (long long)end_time - (long long)start_time;
double elapsed_time = (double)elapsed_ticks / CLOCKS_PER_SEC;
// 获取当前时间
time_t current_time;
time(¤t_time);
printf("完成写入的时间: %s", ctime(¤t_time));
printf("写入完成耗时: %.6f 秒\n", elapsed_time);
return 0;
}
读文件
#include <stdio.h>
int main(void) {
FILE* file;
file = fopen("output.txt", "r");
if (file == NULL) {
perror("failed to open file");
return 1;
}
int ch;
while ((ch = getc(file)) != EOF) {
putchar(ch);
}
fclose(file);
return 0;
}
菜单浏览
/* menuette.c -- 菜单程序 */
#include <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
char get_choice(void);
char get_first(void);
int get_int(void);
void count(void);
int main(void)
{
int choice;
while ((choice = get_choice()) != 'q')
{
switch (choice)
{
case 'a':
printf("Buy low,sell high.\n");
break;
case 'b':
putchar('\a'); /* ANSI*/
break;
case 'c':
count();
break;
default:
printf("Program error!\n");
break;
}
}
printf("Bye.\n");
system("pause");
return 0;
}
void count(void)
{
int n, i;
printf("Count how far? Enter an integer:\n");
n = get_int();
for (i = 1; i <= n; i++)
printf("%d\n", i);
while (getchar() != '\n')
continue;
}
char get_choice(void)
{
int ch;
printf("Enter the letter of your choice:\n");
printf("a.advice b.bell\n");
printf("c.count q.quit\n");
ch = get_first();
while ((ch < 'a' || ch > 'c') && ch != 'q')
{
printf("Please respond with a,b,c,or q.\n");
ch = get_first();
}
return ch;
}
char get_first(void)
{
int ch;
ch = getchar();
while (getchar() != '\n')
continue;
return ch;
}
int get_int(void)
{
int input;
char ch;
while (scanf("%d", &input) != 1)
{
while ((ch = getchar()) != '\n')
putchar(ch); // 处理错误输出
printf(" is not an integer.\nPlease enter an");
printf("integer value,such as 15,-178,or 3:");
}
return input;
}
2024-07-30 第九章:函数
优点:简单,代码量少。
缺点:fibonaci() 函数使用了双递归,函数每一级递归都要调用本身两次,每次递归都要创建新的变量,指数增长的变量数量会很快消耗计算机的内存,很可能导致程序崩溃
#include <stdio.h>
#include <stdlib.h>
int fibonaci(int i);
int main()
{
int i;
for (i = 0; i < 10; i++)
{
printf("%d\t\n", fibonaci(i));
}
system("pause");
return 0;
}
int fibonaci(int i)
{
if (i == 0)
{
return 0;
}
if (i == 1)
{
return 1;
}
return fibonaci(i - 1) + fibonaci(i - 2);
}
2024-07-30 第十章:数组和指针
未赋值的指针,初始化NULL,养成良好的习惯。
指针比较
#include <stdio.h>
#include <stdlib.h>
const int MAX = 3;
int main(void)
{
int var[] = {10, 100, 200};
int i, *ptr;
/* 指针中第一个元素的地址 */
ptr = var;
i = 0;
while (ptr <= &var[MAX - 1])
{
printf("存储地址:var[%d] = %p\n", i, ptr);
printf("存储值:var[%d] = %d\n", i, *ptr);
/* 指向上一个位置 */
ptr++;
i++;
}
system("pause");
return 0;
}
2024-07-30 第十一章:字符串和字符串数组
地址:
#include <stdio.h>
#include <stdlib.h>
#define MSG "I like C++"
int main(void)
{
char ar[] = MSG;
const char *p = MSG;
printf("%p\n", "I like C++");
printf("%p\n", MSG);
printf("%p\n", ar);
printf("%p\n", p);
printf("%p\n", "I like C++");
system("pause");
return 0;
}
在您的代码中,`ar` 和 `p` 指向的地址不一样,因为它们是两种不同类型的数据对象,它们在内存中的存储方式和位置不同。
1. `char ar[] = MSG;`:
- `ar` 是一个字符数组,它在栈上分配内存。当您用 `MSG` 初始化 `ar` 时,编译器会在栈上为 `ar` 分配一块内存,并将字符串 `"I like C++"` 的内容复制到这块内存中。因此,`ar` 的地址是这块栈内存的地址。
2. `const char *p = MSG;`:
- `p` 是一个指向常量字符的指针。当您用 `MSG` 初始化 `p` 时,`p` 指向的是字符串常量 `"I like C++"` 在内存中的位置。字符串常量通常存储在程序的数据段(只读数据段)中,因此 `p` 的地址是这个只读数据段的地址。
由于 `ar` 和 `p` 是两种不同类型的数据对象,它们在内存中的存储位置不同,因此它们的地址也不一样。
总结一下,`ar` 是一个在栈上分配的字符数组,而 `p` 是一个指向只读数据段中字符串常量的指针。这就是为什么 `ar` 和 `p` 指向的地址不一样的原因。
gets()
在C语言中,gets()
函数用于从标准输入(通常是键盘)读取一行字符,并将其存储在指定的字符数组中。然而,需要注意的是,gets()
函数存在严重的安全问题,因为它不检查输入缓冲区的大小,可能导致缓冲区溢出。因此,在现代的C标准库中,gets()
函数已经被弃用,并从C11标准中移除。
gets()
函数的原型如下:
char *gets(char *str);
参数:
str
:指向一个字符数组的指针,用于存储读取的字符串。
返回值:
- 成功时,返回指向
str
的指针。 - 如果遇到文件结束符(EOF)且没有读取到任何字符,或者发生读取错误,返回
NULL
。
用法示例:
#include <stdio.h>
int main() {
char buffer[100];
printf("Enter a string: ");
if (gets(buffer) != NULL) {
printf("You entered: %s\n", buffer);
} else {
printf("Error reading input.\n");
}
return 0;
}
由于 gets()
函数的安全问题,推荐使用 fgets()
函数来替代它。fgets()
函数提供了对输入缓冲区大小的检查,从而避免了缓冲区溢出的风险。
fgets()
函数的原型如下:
char *fgets(char *str, int n, FILE *stream);
参数:
str
:指向一个字符数组的指针,用于存储读取的字符串。n
:要读取的最大字符数(包括终止的空字符)。stream
:输入流,通常是stdin
表示标准输入。
返回值:
- 成功时,返回指向
str
的指针。 - 如果遇到文件结束符(EOF)且没有读取到任何字符,或者发生读取错误,返回
NULL
。
用法示例:
#if 0
#include <stdio.h>
int main() {
char buffer[100];
printf("Enter a string: ");
if (fgets(buffer, sizeof(buffer), stdin) != NULL) {
printf("You entered: %s\n", buffer);
}
else {
printf("Error reading input.\n");
}
return 0;
}
#endif
#include <stdio.h>
int main() {
char buffer[100];
printf("Enter a string: ");
char* result = fgets(buffer, sizeof(buffer), stdin);
if (result != NULL) {
printf("You entered: %s\n", buffer);
}
else {
printf("Error reading input or end of file reached.\n");
}
return 0;
}
使用 fgets()
函数时,确保输入的字符数不超过缓冲区的大小,从而避免缓冲区溢出的风险。
在C语言中,fputs()
函数用于将一个字符串写入指定的输出流(例如文件或标准输出),但不会自动在字符串末尾添加换行符。fputs()
函数是 stdio.h
头文件中定义的一个标准库函数。
fputs()
函数的原型如下:
int fputs(const char *str, FILE *stream);
参数:
str
:指向要输出的字符串的指针。stream
:指向输出流的指针,例如stdout
表示标准输出,stderr
表示标准错误输出,或者是一个文件指针。
返回值:
- 成功时,返回一个非负数。
- 如果发生错误,返回
EOF
(通常是 -1)。
用法示例:
#include <stdio.h>
int main() {
char str[] = "Hello, World!";
int result = fputs(str, stdout);
if (result == EOF) {
printf("Error writing to output.\n");
}
return 0;
}
在这个示例中,fputs()
函数将字符串 "Hello, World!"
输出到标准输出(通常是终端或控制台),但不会自动添加换行符。如果输出成功,fputs()
返回一个非负数;如果发生错误,返回 EOF
。
与 puts()
函数不同,fputs()
不会自动添加换行符,因此可以更灵活地控制输出格式。如果需要输出换行符,可以在字符串末尾手动添加 '\n'
。
例如:
#include <stdio.h>
int main() {
char str[] = "Hello, World!\n";
int result = fputs(str, stdout);
if (result == EOF) {
printf("Error writing to output.\n");
}
return 0;
}
在这个示例中,字符串 "Hello, World!\n"
包含了一个换行符,因此输出时会自动换行。
gets_s()
是 C11 标准中引入的一个函数,用于替代已弃用的 gets()
函数。gets_s()
提供了对输入缓冲区大小的检查,从而避免了缓冲区溢出的风险。然而,需要注意的是,C11 标准是可选的,并非所有编译器都完全支持 C11 标准,因此 gets_s()
可能不是在所有环境中都可用。
gets_s()
函数的原型如下:
char *gets_s(char *str, rsize_t n);
参数:
str
:指向一个字符数组的指针,用于存储读取的字符串。n
:要读取的最大字符数(包括终止的空字符)。
返回值:
- 成功时,返回指向
str
的指针。 - 如果遇到文件结束符(EOF)且没有读取到任何字符,或者发生读取错误,或者输入长度超过了
n-1
个字符,返回NULL
,并且调用errno
设置为ERANGE
。
用法示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
char buffer[100];
printf("Enter a string: ");
if (gets_s(buffer, sizeof(buffer)) != NULL) {
printf("You entered: %s\n", buffer);
} else {
printf("Error reading input.\n");
}
return 0;
}
在这个示例中,gets_s()
函数从标准输入读取一行字符,并将其存储在 buffer
数组中。如果读取成功,gets_s()
返回指向 buffer
的指针,程序会打印输入的字符串。如果读取失败或输入长度超过了 buffer
的大小,gets_s()
返回 NULL
,程序会打印错误消息。
需要注意的是,由于 gets_s()
是 C11 标准的一部分,如果您的编译器不支持 C11 标准,或者您没有启用 C11 标准,gets_s()
函数可能不可用。在这种情况下,推荐使用 fgets()
函数来替代 gets()
和 gets_s()
。
s_gets()
并不是C标准库中的一个函数,但它是一个常见的自定义函数,用于安全地读取字符串,避免缓冲区溢出的问题。这个函数通常是由程序员自己实现的,目的是替代不安全的 gets()
函数。
以下是一个典型的 s_gets()
函数的实现:
#include <stdio.h>
char *s_gets(char *str, int n) {
char *ret_val;
int i = 0;
ret_val = fgets(str, n, stdin);
if (ret_val) {
while (str[i] != '\n' && str[i] != '\0')
i++;
if (str[i] == '\n')
str[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
这个 s_gets()
函数的工作原理如下:
- 使用
fgets()
函数从标准输入读取最多n-1
个字符,并将它们存储在str
中。 - 如果读取成功,检查字符串中是否包含换行符
'\n'
。 - 如果找到了换行符,将其替换为空字符
'\0'
,表示字符串的结束。 - 如果没有找到换行符,说明输入的字符数超过了
n-1
个,此时需要清空输入缓冲区中剩余的字符,直到遇到换行符。
使用 s_gets()
函数的示例:
#include <stdio.h>
int main() {
char buffer[100];
printf("Enter a string: ");
if (s_gets(buffer, sizeof(buffer)) != NULL) {
printf("You entered: %s\n", buffer);
} else {
printf("Error reading input.\n");
}
return 0;
}
在这个示例中,s_gets()
函数从标准输入读取一行字符,并将其存储在 buffer
数组中。如果读取成功,s_gets()
返回指向 buffer
的指针,程序会打印输入的字符串。如果读取失败,s_gets()
返回 NULL
,程序会打印错误消息。
s_gets()
函数通过使用 fgets()
并处理换行符,确保了输入的安全性,避免了缓冲区溢出的风险。
字符串处理函数
C语言标准库提供了许多用于处理字符串的函数,这些函数定义在 <string.h>
头文件中。以下是一些常用的字符串函数及其原型和使用方法:
-
strlen()
- 计算字符串的长度- 原型:
size_t strlen(const char *str);
- 用法:
strlen()
函数返回字符串str
的长度,不包括终止的空字符'\0'
。
#include <stdio.h> #include <string.h> int main() { char str[] = "Hello, World!"; size_t len = strlen(str); printf("Length of string: %zu\n", len); // 输出:Length of string: 13 return 0; }
- 原型:
-
strcpy()
- 复制字符串- 原型:
char *strcpy(char *dest, const char *src);
- 用法:
strcpy()
函数将字符串src
复制到dest
,包括终止的空字符'\0'
。
#include <stdio.h> #include <string.h> int main() { char src[] = "Hello, World!"; char dest[50]; strcpy(dest, src); printf("Copied string: %s\n", dest); // 输出:Copied string: Hello, World! return 0; }
- 原型:
-
strcat()
- 连接字符串- 原型:
char *strcat(char *dest, const char *src);
- 用法:
strcat()
函数将字符串src
连接到dest
的末尾。
#include <stdio.h> #include <string.h> int main() { char dest[50] = "Hello, "; char src[] = "World!"; strcat(dest, src); printf("Concatenated string: %s\n", dest); // 输出:Concatenated string: Hello, World! return 0; }
- 原型:
-
strcmp()
- 比较字符串- 原型:
int strcmp(const char *str1, const char *str2);
- 用法:
strcmp()
函数比较两个字符串str1
和str2
,返回一个整数值表示比较结果。
#include <stdio.h> #include <string.h> int main() { char str1[] = "Hello"; char str2[] = "World"; int result = strcmp(str1, str2); if (result < 0) { printf("str1 is less than str2\n"); } else if (result > 0) { printf("str1 is greater than str2\n"); } else { printf("str1 is equal to str2\n"); } return 0; }
- 原型:
-
strchr()
- 查找字符- 原型:
char *strchr(const char *str, int c);
- 用法:
strchr()
函数在字符串str
中查找字符c
的第一次出现,返回指向该字符的指针。
#include <stdio.h> #include <string.h> int main() { char str[] = "Hello, World!"; char *ptr = strchr(str, 'W'); if (ptr != NULL) { printf("Character found at position: %ld\n", ptr - str); // 输出:Character found at position: 7 } else { printf("Character not found\n"); } return 0; }
- 原型:
-
strstr()
- 查找子字符串- 原型:
char *strstr(const char *haystack, const char *needle);
- 用法:
strstr()
函数在字符串haystack
中查找子字符串needle
的第一次出现,返回指向该子字符串的指针。
#include <stdio.h> #include <string.h> int main() { char haystack[] = "Hello, World!"; char needle[] = "World"; char *ptr = strstr(haystack, needle); if (ptr != NULL) { printf("Substring found at position: %ld\n", ptr - haystack); // 输出:Substring found at position: 7 } else { printf("Substring not found\n"); } return 0; }
- 原型:
这些函数提供了基本的字符串操作功能,但在使用时需要注意缓冲区溢出的问题,特别是在使用 strcpy()
和 strcat()
时,确保目标缓冲区有足够的空间来存储结果。
2024-07-30 第十二章:存储类别、链接和内存管理
在C语言中,变量的存储类别(storage class)决定了变量的作用域(scope)、生命周期(lifetime)和链接属性(linkage)。C语言中有五种主要的存储类别:
-
自动(auto):
- 作用域:局部于定义它的代码块(通常是函数或代码块内部)。
- 生命周期:从进入代码块时创建,到退出代码块时销毁。
- 链接属性:无链接属性,即变量只在定义它的代码块内可见。
- 默认情况下,函数内部的变量(不使用任何存储类别说明符)都是自动变量。
void example_function() { auto int x = 10; // 显式使用 auto 关键字 int y = 20; // 隐式自动变量 }
-
静态(static):
- 作用域:局部于定义它的代码块(通常是函数或代码块内部),但在整个程序运行期间都存在。
- 生命周期:从程序开始运行时创建,到程序结束时销毁。
- 链接属性:无链接属性(局部静态变量)或内部链接属性(全局静态变量)。
- 局部静态变量在函数调用之间保持其值。
void example_function() { static int count = 0; // 静态局部变量 count++; printf("Count: %d\n", count); }
-
寄存器(register):
- 作用域:局部于定义它的代码块(通常是函数或代码块内部)。
- 生命周期:从进入代码块时创建,到退出代码块时销毁。
- 链接属性:无链接属性。
- 寄存器变量建议编译器将其存储在CPU的寄存器中,以提高访问速度,但编译器可以选择忽略这个建议。
void example_function() { register int i; // 寄存器变量 for (i = 0; i < 1000; i++) { // 一些操作 } }
-
外部(extern):
- 作用域:全局(整个程序)。
- 生命周期:从程序开始运行时创建,到程序结束时销毁。
- 链接属性:外部链接属性,即变量可以被其他文件中的代码访问。
- 外部变量用于声明在其他文件中定义的全局变量。
// file1.c int global_var = 10; // file2.c extern int global_var; // 声明外部变量
-
无链接(无说明符):
- 作用域:全局(整个文件)。
- 生命周期:从程序开始运行时创建,到程序结束时销毁。
- 链接属性:无链接属性,即变量只在定义它的文件内可见。
- 这种变量通常在文件的顶部定义,但没有使用
static
或extern
关键字。
int global_var = 10; // 无链接属性的全局变量 void example_function() { // 使用 global_var }
这些存储类别提供了灵活的机制来控制变量的作用域和生命周期,使得程序员可以根据需要管理内存和数据访问。
随机数函数:
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
int main()
{
int i, n;
time_t t;
n = 5;
/* 初始化随机数发生器 */
srand((unsigned) time(&t));
/* 输出 0 到 49 之间的 5 个随机数 */
for( i = 0 ; i < n ; i++ ) {
printf("%d\n", rand() % 50);
}
return(0);
}
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main()
{
char name[100];
char* description;
strcpy(name, "Zara Ali");
/* 动态分配内存 */
description = (char*)malloc(200 * sizeof(char));
if (description == NULL)
{
fprintf(stderr, "Error - unable to allocate required memory\n");
}
else
{
strcpy(description, "Zara ali a DPS student in class 10th");
}
printf("Name = %s\n", name);
printf("Description: %s\n", description);
}
malloc
和 realloc
都是 C 标准库中用于动态内存分配的函数,但它们有不同的用途和行为:
-
malloc
:- 用途:
malloc
用于分配指定大小的内存块。 - 原型:
void *malloc(size_t size);
- 参数:
size
是要分配的内存字节数。 - 返回值:成功时返回指向分配内存的指针,失败时返回
NULL
。 - 行为:
malloc
分配的内存块的内容是未初始化的,即它包含之前存储在该内存位置的任何数据。
示例:
int *ptr = (int*)malloc(10 * sizeof(int)); if (ptr == NULL) { // 处理内存分配失败的情况 }
- 用途:
-
realloc
:- 用途:
realloc
用于调整已分配内存块的大小。 - 原型:
void *realloc(void *ptr, size_t new_size);
- 参数:
ptr
是指向先前通过malloc
、calloc
或realloc
分配的内存块的指针,new_size
是新的内存字节数。 - 返回值:成功时返回指向新内存块的指针,失败时返回
NULL
。如果realloc
失败,原来的内存块保持不变。 - 行为:
- 如果
new_size
大于原来的大小,新分配的部分是未初始化的。 - 如果
new_size
小于原来的大小,原内存块中超出new_size
的部分数据会丢失。 realloc
可能会在新的内存位置分配内存,并将原内存块的内容复制到新位置。因此,调用realloc
后,应该使用返回的新指针,而不是原来的指针。
- 如果
示例:
int *ptr = (int*)malloc(10 * sizeof(int)); if (ptr == NULL) { // 处理内存分配失败的情况 } // 调整内存块大小 int *new_ptr = (int*)realloc(ptr, 20 * sizeof(int)); if (new_ptr == NULL) { // 处理内存分配失败的情况 } else { ptr = new_ptr; // 更新指针 }
- 用途:
总结:
malloc
用于初始分配内存。realloc
用于调整已分配内存的大小,可能会在新的内存位置分配内存并复制原内存块的内容。
在使用 realloc
时,需要注意以下几点:
- 确保
ptr
不是NULL
,因为realloc(NULL, size)
等价于malloc(size)
。 - 处理
realloc
返回的指针,确保在内存分配失败时不会丢失原来的内存块。
2024-07-31 第十三章:文件输入/输出
文件IO
#if 0
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const int MAX = 41;
int main(void)
{
FILE* fp;
char words[MAX];
//打开失败报错,没有就会创建
if ((fp = fopen("wordy", "a+")) == NULL)
{
fprintf(stderr, "Can't open \"wordy\" file.\n");
exit(EXIT_FAILURE);
}
puts("Enter words to add to the file; press the #");
//puts会在结尾自动添加换行符
puts("key at the beginning of a line to terminate.");
//读取最多40个字符到words里面,并且第一个字符不能是#
while ((fscanf(stdin, "%40s", words) == 1) && (words[0] != '#') && (words[0] != '#'))
fprintf(fp, "%s\n", words);
puts("File contents:");
rewind(fp); /*返回到文件开始处*/
//读取里面所有的字符串:
while (fscanf(fp, "%40s", words) == 1)
puts(words);
puts("Done!");
if (fclose(fp) != 0)
fprintf(stderr, "Error closing file\n");
system("pause");
return 0;
}
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
const int MAX = 41;
int main(void)
{
FILE* fp;
char words[MAX];
// 打开失败报错
if ((fp = fopen("wordy", "a+")) == NULL)
{
fprintf(stderr, "Can't open \"wordy\" file.\n");
exit(EXIT_FAILURE);
}
puts("Enter words to add to the file; press the #");
puts("key at the beginning of a line to terminate.");
// 读取整行输入
while (fgets(words, MAX, stdin) != NULL)
{
// 移除换行符
words[strcspn(words, "\n")] = '\0';
// 检查第一个字符是否是#
if (words[0] == '#')
break;
// 写入文件
fprintf(fp, "%s\n", words);
}
puts("File contents:");
rewind(fp); /*返回到文件开始处*/
while (fscanf(fp, "%40s", words) == 1)
puts(words);
puts("Done!");
if (fclose(fp) != 0)
fprintf(stderr, "Error closing file\n");
return 0;
}
随机访问
fseek()
和 ftell()
是C语言标准库中用于文件随机访问的两个函数。它们允许你在文件中任意移动文件指针,从而实现对文件内容的随机访问。
fseek()
函数
fseek()
函数用于设置文件指针的位置。它的原型定义在 <stdio.h>
头文件中,形式如下:
int fseek(FILE *stream, long offset, int origin);
FILE *stream
:指向要操作的文件流的指针。long offset
:相对于origin
的偏移量。int origin
:起始位置,可以是以下三个宏之一:SEEK_SET
:文件开头。SEEK_CUR
:当前文件指针位置。SEEK_END
:文件末尾。
fseek()
函数将文件指针移动到 origin
加上 offset
的位置。如果操作成功,返回 0;否则返回非零值。
ftell()
函数
ftell()
函数用于获取文件指针的当前位置。它的原型定义在 <stdio.h>
头文件中,形式如下:
long ftell(FILE *stream);
FILE *stream
:指向要操作的文件流的指针。
ftell()
函数返回文件指针相对于文件开头的偏移量。如果发生错误,返回 -1L
。
示例代码
以下是一个示例代码,演示如何使用 fseek()
和 ftell()
函数进行文件随机访问:
#include <stdio.h>
int main(void)
{
FILE *fp;
long size;
// 打开文件
fp = fopen("example.txt", "r");
if (fp == NULL)
{
perror("Error opening file");
return -1;
}
// 移动文件指针到文件末尾
if (fseek(fp, 0L, SEEK_END) != 0)
{
perror("Error seeking to end of file");
fclose(fp);
return -1;
}
// 获取文件大小
size = ftell(fp);
if (size == -1L)
{
perror("Error getting file size");
fclose(fp);
return -1;
}
printf("File size: %ld bytes\n", size);
// 关闭文件
fclose(fp);
return 0;
}
在这个示例中:
- 使用
fopen()
打开文件example.txt
以只读模式。 - 使用
fseek(fp, 0L, SEEK_END)
将文件指针移动到文件末尾。 - 使用
ftell(fp)
获取文件指针的当前位置,即文件大小。 - 打印文件大小并关闭文件。
总结:
fseek()
和 ftell()
函数允许你在文件中任意移动文件指针,从而实现对文件内容的随机访问。fseek()
设置文件指针的位置,ftell()
获取文件指针的当前位置。
2024-07-31 第十四章:结构和其他数据形式
结构体构造:
struct tag {
member-list
member-list
member-list
...
} variable-list ;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//同时又声明了结构体变量s1
//这个结构体并没有标明其标签
struct
{
int a;
char b;
double c;
} s1;
//此声明声明了拥有3个成员的结构体,分别为整型的a,字符型的b和双精度的c
//结构体的标签被命名为SIMPLE,没有声明变量
struct SIMPLE
{
int a;
char b;
double c;
};
//用SIMPLE标签的结构体,另外声明了变量t1、t2、t3
struct SIMPLE t1, t2[20], *t3;
//也可以用typedef创建新类型
typedef struct
{
int a;
char b;
double c;
} Simple2;
//现在可以用Simple2作为类型声明新的结构体变量
Simple2 u1, u2[20], *u3;
//此结构体的声明包含了其他的结构体
struct COMPLEX
{
char string[100];
struct SIMPLE a;
};
//此结构体的声明包含了指向自己类型的指针
struct NODE
{
char string[100];
struct NODE *next_node;
};
struct B; //对结构体B进行不完整声明
//结构体A中包含指向结构体B的指针
struct A
{
struct B *partner;
//other members;
};
//结构体B中包含指向结构体A的指针,在A声明完后,B也随之进行声明
struct B
{
struct A *partner;
//other members;
};
结构体初始化:
#include <stdio.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} book = {"C 语言", "RUNOOB", "编程语言", 123456};
int main()
{
printf("title : %s\nauthor: %s\nsubject: %s\nbook_id: %d\n", book.title, book.author, book.subject, book.book_id);
}
访问成员:
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 输出 Book1 信息 */
printf( "Book 1 title : %s\n", Book1.title);
printf( "Book 1 author : %s\n", Book1.author);
printf( "Book 1 subject : %s\n", Book1.subject);
printf( "Book 1 book_id : %d\n", Book1.book_id);
/* 输出 Book2 信息 */
printf( "Book 2 title : %s\n", Book2.title);
printf( "Book 2 author : %s\n", Book2.author);
printf( "Book 2 subject : %s\n", Book2.subject);
printf( "Book 2 book_id : %d\n", Book2.book_id);
return 0;
}
结构体作为函数参数:
#include <stdio.h>
#include <string.h>
struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
};
/* 函数声明 */
void printBook( struct Books book );
int main( )
{
struct Books Book1; /* 声明 Book1,类型为 Books */
struct Books Book2; /* 声明 Book2,类型为 Books */
/* Book1 详述 */
strcpy( Book1.title, "C Programming");
strcpy( Book1.author, "Nuha Ali");
strcpy( Book1.subject, "C Programming Tutorial");
Book1.book_id = 6495407;
/* Book2 详述 */
strcpy( Book2.title, "Telecom Billing");
strcpy( Book2.author, "Zara Ali");
strcpy( Book2.subject, "Telecom Billing Tutorial");
Book2.book_id = 6495700;
/* 输出 Book1 信息 */
printBook( Book1 );
/* 输出 Book2 信息 */
printBook( Book2 );
return 0;
}
void printBook( struct Books book )
{
printf( "Book title : %s\n", book.title);
printf( "Book author : %s\n", book.author);
printf( "Book subject : %s\n", book.subject);
printf( "Book book_id : %d\n", book.book_id);
}
类型转换写法
#include <stdio.h>
struct book {
char title[100];
char author[100];
float price;
};
void print_book(struct book b) {
printf("Title: %s\n", b.title);
printf("Author: %s\n", b.author);
printf("Price: %.2f\n", b.price);
}
int main(void) {
// 初始化一个 struct book 类型的变量
struct book b1 = (struct book) {"The Idiot", "Fyodor Dostoyevsky", 6.99};
// 打印结构体内容
print_book(b1);
return 0;
}
共用体:
union [union tag]
{
member definition;
member definition;
...
member definition;
} [one or more union variables];
union Data
{
int i;
float f;
char str[20];
} data;
#include <stdio.h>
#include <string.h>
union Data
{
int i;
float f;
char str[20];
};
int main( )
{
union Data data;
printf( "Memory size occupied by data : %d\n", sizeof(data));
return 0;
}
在C语言中,共用体(union)是一种特殊的数据类型,它允许在相同的内存位置存储不同的数据类型。共用体的所有成员共享同一块内存,因此任何时候只能有一个成员包含有效值。
访问共用体成员的方式与访问结构体成员类似,可以使用点运算符 .
或箭头运算符 ->
(如果使用指针)。
以下是一个简单的示例,演示如何定义、初始化和访问共用体成员:
#include <stdio.h>
// 定义一个共用体
union Data {
int i;
float f;
char str[20];
};
int main(void)
{
// 声明一个共用体变量
union Data data;
// 初始化并访问共用体成员
data.i = 10;
printf("data.i: %d\n", data.i);
data.f = 220.5;
printf("data.f: %.1f\n", data.f);
strcpy(data.str, "C Programming");
printf("data.str: %s\n", data.str);
return 0;
}
在这个示例中:
- 定义了一个名为
Data
的共用体,包含三个成员:一个整型i
、一个浮点型f
和一个字符数组str
。 - 声明了一个
Data
类型的共用体变量data
。 - 使用点运算符
.
访问并初始化共用体成员:data.i = 10;
将整型成员i
初始化为 10。data.f = 220.5;
将浮点型成员f
初始化为 220.5。strcpy(data.str, "C Programming");
将字符数组成员str
初始化为字符串 “C Programming”。
- 使用
printf
函数打印共用体成员的值。
需要注意的是,由于共用体的所有成员共享同一块内存,因此在任何时候只能有一个成员包含有效值。在上面的示例中,每次赋值操作都会覆盖前一个成员的值。
总结:
访问共用体成员的方式与访问结构体成员类似,可以使用点运算符 .
或箭头运算符 ->
(如果使用指针)。由于共用体的所有成员共享同一块内存,因此在任何时候只能有一个成员包含有效值。
枚举:
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
#include <stdio.h>
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
int main()
{
enum DAY day;
day = WED;
printf("%d",day);
return 0;
}
#include <stdio.h>
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
int main()
{
// 遍历枚举元素
for (day = MON; day <= SUN; day++) {
printf("枚举元素:%d \n", day);
}
}
#include <stdio.h>
#include <stdlib.h>
int main()
{
enum color { red=1, green, blue };
enum color favorite_color;
/* 用户输入数字来选择颜色 */
printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
scanf("%u", &favorite_color);
/* 输出结果 */
switch (favorite_color)
{
case red:
printf("你喜欢的颜色是红色");
break;
case green:
printf("你喜欢的颜色是绿色");
break;
case blue:
printf("你喜欢的颜色是蓝色");
break;
default:
printf("你没有选择你喜欢的颜色");
}
return 0;
}
typedef关键词
#include <stdio.h>
#include <string.h>
typedef struct Books
{
char title[50];
char author[50];
char subject[100];
int book_id;
} Book;
int main( )
{
Book book;
strcpy( book.title, "C 教程");
strcpy( book.author, "Runoob");
strcpy( book.subject, "编程语言");
book.book_id = 12345;
printf( "书标题 : %s\n", book.title);
printf( "书作者 : %s\n", book.author);
printf( "书类目 : %s\n", book.subject);
printf( "书 ID : %d\n", book.book_id);
return 0;
}
在C语言中,内存对齐是指数据在内存中的存储位置必须满足一定的对齐要求。这些要求通常由编译器和硬件平台决定,目的是为了提高内存访问的效率。
为什么需要内存对齐?
- 性能优化:现代CPU在访问未对齐的内存时可能会导致性能下降,因为需要额外的指令来处理未对齐的访问。
- 硬件限制:某些硬件平台要求数据必须存储在特定的内存边界上,否则可能会导致硬件异常或错误。
对齐规则
- 基本数据类型:通常,基本数据类型(如
int
,float
,double
,char
等)的对齐要求等于其自身的大小(以字节为单位)。例如,一个int
通常需要4字节对齐。 - 结构体和联合体:结构体和联合体的对齐要求通常是其成员中最大的对齐要求。编译器会在结构体成员之间插入填充字节(padding)以满足对齐要求。
示例
考虑以下结构体:
struct Example {
char a; // 1 byte
int b; // 4 bytes
short c; // 2 bytes
};
假设编译器要求int
类型4字节对齐,short
类型2字节对齐。那么结构体的内存布局可能是:
| a | pad | b | b | b | b | c | c | pad |
其中,pad
表示填充字节。这样,int b
可以4字节对齐,short c
可以2字节对齐。
控制对齐
C语言提供了一些方法来控制内存对齐:
-
#pragma pack
:编译器指令,用于指定结构体的对齐方式。#pragma pack(push, 1) // 1字节对齐 struct Example { char a; int b; short c; }; #pragma pack(pop)
-
_Alignas
:C11标准引入的关键字,用于指定变量或类型的对齐要求。_Alignas(16) int x; // x 必须16字节对齐
-
alignof
:C11标准引入的操作符,用于获取类型的对齐要求。size_t alignment = alignof(int); // 获取int类型的对齐要求
注意事项
- 性能影响:过度使用非标准对齐可能会影响性能。
- 可移植性:不同的编译器和平台可能有不同的对齐规则,因此需要谨慎处理。
通过理解和合理使用内存对齐,可以编写出更高效和可移植的C代码。
2024-08-01 第十五章:位操作
在C语言中,按位运算符用于对整数在内存中的二进制位进行操作。以下是C语言中常用的按位运算符及其功能:
-
按位与(&):
- 对两个操作数的每一位执行逻辑与操作。只有当两个相应的二进制位都为1时,结果才为1。
- 例如:
5 & 3
的结果是1
,因为0101 & 0011 = 0001
。
-
按位或(|):
- 对两个操作数的每一位执行逻辑或操作。只要两个相应的二进制位中有一个为1,结果就为1。
- 例如:
5 | 3
的结果是7
,因为0101 | 0011 = 0111
。
-
按位异或(^):
- 对两个操作数的每一位执行逻辑异或操作。当两个相应的二进制位不同时,结果为1,否则为0。
- 例如:
5 ^ 3
的结果是6
,因为0101 ^ 0011 = 0110
。
-
按位取反(~):
- 对操作数的每一位执行逻辑取反操作。即1变为0,0变为1。
- 例如:
~5
的结果是-6
,因为~0101 = 1010
(在补码表示中,1010表示-6)。
-
左移(<<):
- 将操作数的所有位向左移动指定的位数。左移n位相当于乘以2的n次方。
- 例如:
5 << 1
的结果是10
,因为0101 << 1 = 1010
。
-
右移(>>):
- 将操作数的所有位向右移动指定的位数。右移n位相当于除以2的n次方。
- 例如:
5 >> 1
的结果是2
,因为0101 >> 1 = 0010
。
这些按位运算符在处理位级操作时非常有用,例如在嵌入式系统编程、加密算法和网络协议中。使用这些运算符时,需要注意数据类型的位数和符号位的影响。
位运算在编程中常用于处理二进制数据,特别是在需要对特定的位进行操作时。以下是一些常见的位操作及其用法:
- 掩码(Mask)
掩码是一个用于屏蔽或选择特定位的值。通常使用十六进制或二进制表示。
例如,掩码 0x0F
表示选择低四位,而 0xF0
表示选择高四位。
2. 打开位(Set Bit)
打开位意味着将特定的位设置为1。使用按位或运算符(|
)和掩码来实现。
unsigned int num = 0; // 初始值为0
unsigned int mask = 1 << 2; // 打开第三位(从0开始计数)
num = num | mask; // 现在 num 的第三位是1
3. 关闭位(Clear Bit)
关闭位意味着将特定的位设置为0。使用按位与运算符(&
)和掩码的取反来实现。
unsigned int num = 0xFF; // 初始值为所有位都是1
unsigned int mask = ~(1 << 2); // 关闭第三位(从0开始计数)
num = num & mask; // 现在 num 的第三位是0
4. 清空位(Clear Bit)
清空位与关闭位是相同的操作,即将特定的位设置为0。
unsigned int num = 0xFF; // 初始值为所有位都是1
unsigned int mask = ~(1 << 2); // 清空第三位(从0开始计数)
num = num & mask; // 现在 num 的第三位是0
5. 切换位(Toggle Bit)
切换位意味着将特定的位取反,即如果该位是1则变为0,如果是0则变为1。使用按位异或运算符(^
)和掩码来实现。
unsigned int num = 0x0F; // 初始值为低四位是1
unsigned int mask = 1 << 2; // 切换第三位(从0开始计数)
num = num ^ mask; // 现在 num 的第三位是0
6. 检查位(Check Bit)
检查位意味着判断特定的位是0还是1。使用按位与运算符(&
)和掩码来实现。
unsigned int num = 0x0F; // 初始值为低四位是1
unsigned int mask = 1 << 2; // 检查第三位(从0开始计数)
if (num & mask) {
// 第三位是1
} else {
// 第三位是0
}
这些位操作在处理硬件寄存器、标志位、权限控制等方面非常有用。通过合理使用位运算,可以提高代码的效率和可读性。
2024-08-01 第十六章:C预处理器和C库
在预处理之前,编译器必须对该程序进行一些翻译处理。
-
首先,编译器把源代码中出现的字符映射到源字符集。该过程处理多字节字符和三字符序列——字符扩展让C更加国际化。
-
第二,编译器定位每个反斜杠后面跟着换行符的实例,并删除它们。也就是说,把下面两个物理行(physical line):
printf("That’s wond\
erful!\n");
//转换为
printf(“That’s wonderful\n!”);
#define PSQR(X) printf(“The square of X is %d.\n”, ((X)*(X)));
C语言标准库提供了一个强大的数学库,称为<math.h>
(在C++中是<cmath>
)。这个库包含了许多常用的数学函数,适用于各种数值计算和科学计算。以下是一些常见的数学函数及其功能:
基本数学函数
-
绝对值:
int abs(int x);
:返回整数x
的绝对值。double fabs(double x);
:返回浮点数x
的绝对值。
-
幂函数:
double pow(double base, double exponent);
:返回base
的exponent
次幂。
-
平方根:
double sqrt(double x);
:返回x
的平方根。
-
指数和对数函数:
double exp(double x);
:返回e
的x
次幂。double log(double x);
:返回x
的自然对数(以e
为底)。double log10(double x);
:返回x
的常用对数(以 10 为底)。
三角函数
-
正弦、余弦和正切:
double sin(double x);
:返回x
的正弦值。double cos(double x);
:返回x
的余弦值。double tan(double x);
:返回x
的正切值。
-
反正弦、反余弦和反正切:
double asin(double x);
:返回x
的反正弦值。double acos(double x);
:返回x
的反余弦值。double atan(double x);
:返回x
的反正切值。double atan2(double y, double x);
:返回y/x
的反正切值,使用两个参数的符号来确定结果的象限。
双曲函数
- 双曲正弦、双曲余弦和双曲正切:
double sinh(double x);
:返回x
的双曲正弦值。double cosh(double x);
:返回x
的双曲余弦值。double tanh(double x);
:返回x
的双曲正切值。
其他函数
-
取整函数:
double ceil(double x);
:返回不小于x
的最小整数值。double floor(double x);
:返回不大于x
的最大整数值。
-
取模函数:
double fmod(double x, double y);
:返回x
除以y
的余数。
-
浮点数分解:
double frexp(double value, int *exp);
:将value
分解为尾数和指数,返回尾数,指数存储在exp
指向的整数中。double ldexp(double x, int exp);
:返回x
乘以 2 的exp
次幂。
-
浮点数合成:
double modf(double value, double *iptr);
:将value
分解为整数部分和小数部分,返回小数部分,整数部分存储在iptr
指向的浮点数中。
示例
以下是一个简单的示例,展示了如何使用<math.h>
库中的函数:
#include <stdio.h>
#include <math.h>
int main() {
double x = 2.0;
double y = 3.0;
printf("sqrt(%.2f) = %.2f\n", x, sqrt(x));
printf("pow(%.2f, %.2f) = %.2f\n", x, y, pow(x, y));
printf("sin(%.2f) = %.2f\n", x, sin(x));
printf("exp(%.2f) = %.2f\n", x, exp(x));
printf("log(%.2f) = %.2f\n", x, log(x));
return 0;
}
编译和运行这个程序时,需要链接数学库,通常使用 -lm
选项:
gcc -o math_example math_example.c -lm
./math_example
通过使用<math.h>
库中的函数,可以方便地进行各种数学计算,提高编程效率。
在C语言中,exit()
和atexit()
是两个用于控制程序终止的函数,它们在标准库<stdlib.h>
中定义。
exit()
函数
exit()
函数用于立即终止程序的执行,并返回一个状态码给操作系统。其原型如下:
void exit(int status);
status
:程序的退出状态码。通常,0
表示正常退出,非零值表示异常退出。
示例:
#include <stdio.h>
#include <stdlib.h>
int main() {
printf("Program is starting...\n");
exit(0); // 立即终止程序,返回状态码0
printf("This line will not be executed.\n");
return 0;
}
atexit()
函数
atexit()
函数用于注册一个函数,该函数在程序正常终止时被调用。其原型如下:
int atexit(void (*func)(void));
func
:一个不接受任何参数且不返回任何值的函数指针。
atexit()
函数返回一个整数值:
- 如果注册成功,返回
0
。 - 如果注册失败,返回一个非零值。
示例:
#include <stdio.h>
#include <stdlib.h>
void cleanup() {
printf("Performing cleanup before exiting...\n");
}
int main() {
if (atexit(cleanup) != 0) {
fprintf(stderr, "Failed to register cleanup function.\n");
exit(1);
}
printf("Program is running...\n");
exit(0); // 程序正常终止,调用cleanup函数
printf("This line will not be executed.\n");
return 0;
}
在这个示例中,cleanup
函数在程序正常终止时被调用,执行一些清理工作。
注意事项
- 调用顺序:通过
atexit()
注册的函数在程序终止时按照注册的逆序调用。 - 多次注册:可以多次调用
atexit()
注册多个函数,它们都会在程序终止时被调用。 - 异常终止:如果程序通过
abort()
或信号(如SIGINT
)异常终止,atexit()
注册的函数可能不会被调用。
通过合理使用exit()
和atexit()
函数,可以更好地控制程序的终止行为,确保资源的正确释放和清理。
qsort()
函数是C标准库中提供的快速排序函数,定义在<stdlib.h>
头文件中。它用于对数组进行排序,可以处理任意类型的数据。qsort()
函数的原型如下:
void qsort(void *base, size_t nmemb, size_t size, int (*compar)(const void *, const void *));
参数说明
base
:指向要排序的数组的起始地址。nmemb
:数组中元素的数量。size
:每个元素的大小(以字节为单位)。compar
:指向比较函数的指针,该函数用于比较两个元素。
比较函数
比较函数的原型必须如下:
int compar(const void *a, const void *b);
a
和b
是指向要比较的元素的指针。- 比较函数的返回值:
- 如果
*a
小于*b
,返回负整数。 - 如果
*a
等于*b
,返回零。 - 如果
*a
大于*b
,返回正整数。
- 如果
示例
以下是一个使用qsort()
函数对整数数组进行排序的示例:
#include <stdio.h>
#include <stdlib.h>
// 比较函数
int compare_int(const void *a, const void *b) {
int int_a = *(int*)a;
int int_b = *(int*)b;
return int_a - int_b;
}
int main() {
int arr[] = {5, 2, 9, 1, 5, 6};
size_t n = sizeof(arr) / sizeof(arr[0]);
// 使用qsort对数组进行排序
qsort(arr, n, sizeof(int), compare_int);
// 打印排序后的数组
for (size_t i = 0; i < n; i++) {
printf("%d ", arr[i]);
}
printf("\n");
return 0;
}
在这个示例中,compare_int
函数用于比较两个整数。qsort()
函数根据这个比较函数对整数数组进行排序,并输出排序后的结果。
注意事项
- 类型转换:在比较函数中,需要将
void *
类型的指针转换为实际的类型指针。 - 稳定性:
qsort()
函数不保证排序的稳定性,即相等元素的相对顺序可能会改变。 - 性能:
qsort()
函数的性能可能因实现而异,通常适用于通用排序需求。
通过使用qsort()
函数,可以方便地对各种类型的数组进行排序,提高代码的复用性和可维护性。
断言(Assertion)是一种用于在程序中检查条件是否满足的机制。如果条件不满足,断言会触发一个错误,通常会导致程序终止。断言主要用于调试阶段,帮助开发者发现和定位错误。
在C语言中,标准库提供了断言库<assert.h>
,其中包含了一个宏assert()
。以下是关于assert()
的详细介绍:
assert()
宏
assert()
宏用于在运行时检查一个条件是否为真。如果条件为假(即0
),assert()
会向标准错误流(通常是控制台)输出一条错误消息,并调用abort()
函数终止程序。
assert()
宏的原型如下:
void assert(int expression);
expression
:一个整数表达式,如果该表达式为假(即0
),断言失败。
示例
以下是一个使用assert()
宏的简单示例:
#include <stdio.h>
#include <assert.h>
int divide(int a, int b) {
assert(b != 0); // 确保除数不为零
return a / b;
}
int main() {
int a = 10;
int b = 0;
printf("Result: %d\n", divide(a, b));
return 0;
}
在这个示例中,divide
函数在执行除法操作之前使用assert(b != 0)
来确保除数b
不为零。如果b
为零,assert()
会触发一个错误并终止程序。
禁用断言
在发布版本中,通常会禁用断言以提高性能。可以通过定义宏NDEBUG
来禁用断言。在包含<assert.h>
之前定义NDEBUG
,assert()
宏会被定义为一个空操作(即不执行任何操作)。
示例:
#define NDEBUG // 禁用断言
#include <assert.h>
#include <stdio.h>
int main() {
int a = 10;
int b = 0;
assert(b != 0); // 这条断言在NDEBUG定义后不会执行任何操作
printf("Result: %d\n", a / b); // 这里会导致除以零的错误
return 0;
}
注意事项
- 调试工具:断言主要用于调试阶段,帮助开发者发现逻辑错误。在发布版本中,通常会禁用断言。
- 错误处理:断言不应该用于处理用户输入错误或外部错误,这些情况应该使用异常处理或其他错误处理机制。
- 性能影响:启用断言会增加程序的运行时开销,因此在发布版本中应禁用断言。
通过合理使用断言,可以在开发过程中及时发现和修复错误,提高代码的健壮性和可靠性。
stdarg.h
是C标准库中的一个头文件,提供了处理可变参数(variadic arguments)的机制。可变参数是指函数可以接受不定数量的参数,例如常见的printf
和scanf
函数。stdarg.h
头文件中定义了一些宏,用于访问这些可变参数。
以下是stdarg.h
中的一些主要宏和类型:
-
va_list
:这是一个类型,用于存储可变参数的信息。通常在使用可变参数的函数中声明一个va_list
类型的变量。 -
va_start
:这是一个宏,用于初始化va_list
变量,使其指向可变参数列表的第一个参数。 -
va_arg
:这是一个宏,用于从va_list
变量中获取下一个参数,并将其转换为指定的类型。 -
va_end
:这是一个宏,用于清理va_list
变量,确保在函数返回前正确释放资源。
示例
以下是一个简单的示例,展示了如何使用stdarg.h
来实现一个可变参数函数:
#include <stdio.h>
#include <stdarg.h>
// 计算一组整数的和
int sum(int count, ...) {
va_list args;
int total = 0;
// 初始化va_list变量
va_start(args, count);
// 遍历可变参数
for (int i = 0; i < count; i++) {
total += va_arg(args, int);
}
// 清理va_list变量
va_end(args);
return total;
}
int main() {
int result = sum(4, 1, 2, 3, 4);
printf("Sum: %d\n", result); // 输出: Sum: 10
return 0;
}
在这个示例中,sum
函数接受一个整数count
和count
个整数参数。使用va_list
、va_start
、va_arg
和va_end
宏来处理这些可变参数,并计算它们的和。
注意事项
- 类型安全:在使用
va_arg
宏时,必须指定正确的参数类型,否则会导致未定义行为。 - 参数数量:必须确保在调用
va_arg
宏时,有足够的参数可供访问,否则会导致未定义行为。 - 参数类型:可变参数函数通常需要一个固定的参数来指定可变参数的数量或类型,例如
printf
函数中的格式字符串。
通过使用stdarg.h
头文件中的宏,可以方便地实现可变参数函数,提高代码的灵活性和可扩展性。
2024-08-01 第十七章:高级数据表示
// films1.c -- 使用一个结构数组
#include <stdio.h>
#include <string.h>
#define TSIZE 45 // 储存片名的数组大小
#define FMAX 5 // 影片的最大数量
struct film
{
char title[TSIZE];
int rating;
};
char *s_gets(char str[], int lim);
int main(void)
{
struct film movies[FMAX];
int i = 0;
int j;
puts("Enter first movie title:");
while (i < FMAX && s_gets(movies[i].title, TSIZE) != NULL && movies[i].title[0] != '\0')
{
puts("Enter your rating <0-10>:");
scanf("%d", &movies[i++].rating);
while (getchar() != '\n')
continue;
puts("Enter next movie title (empty line to stop):");
}
if (i == 0)
printf("No data entered. ");
else
printf("Here is the movie list:\n");
for (j = 0; j < i; j++)
printf("Movies: %s Rating: %d\n", movies[j].title, movies[j].rating);
printf("Bye!\n");
return 0;
}
char *s_gets(char *st, int n)
{
char *ret_val;
char *find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n'); // 查找换行符
if (find) // 如果地址不是NULL
*find = '\0'; // 在此处放置一个空字符
else
while (getchar() != '\n')
continue; // 处理输入行的剩余字符
}
return ret_val;
}
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define TSIZE 45 //片名大小
struct film {
char title[TSIZE];
int rating;
struct film * next; //指向链表的下一个结构
};
char * s_gets(char * st, int n);
int main(void)
{
struct film * head = NULL;
struct film * prev = NULL, *current = NULL;
char input[TSIZE];
puts("输入第一部电影的名字:");
while (s_gets(input, TSIZE) != NULL && input[0] != '\0')
{
current = (struct film *) malloc(sizeof(struct film));
if (head == NULL)
head = current;
else
prev->next = current;
current->next = NULL;
strcpy(current->title, input);
puts("输入评分<0-10>:");
scanf("%d", ¤t->rating);
while (getchar() != '\n')
continue;
puts("输入下一部电影名字(直接回车可退出)");
prev = current;
}
//显示电影
if (head == NULL)
printf("无数据.");
else
{
printf("电影列表如下:\n");
current = head;
while (current != NULL)
{
printf("电影:%s 评分:%d\n", current->title, current->rating);
current = current->next;
}
}
//释放内存
current = head;
while (head != NULL) //此处和书不同,书上运行出错。我认为这里应该判断head是否NULL而不是current是否为NULL
{
current = head;
head =head->next;
free(current);
}
printf("BYE\n");
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
char * find;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
find = strchr(st, '\n');//查找换行符
if (find)
*find = '\0'; //将换行符换成'\0'
else
while (getchar() != '\n') //处理输入行剩余的字符
continue;
}
return ret_val;
}
//list.h
#pragma once
#include<stdbool.h>
/*特定程序的声明*/
#define TSIZE 45 //存储电影名的数组大小
struct film
{
char title[TSIZE];
int rating;
};
/*一般类型定义*/
typedef struct film Item;
typedef struct node
{
Item item;
struct node * next;
}Node;
typedef Node * List;
/*函数原型*/
/*操作: 初始化一个链表 */
/*前提条件: plist指向一个链表 */
/*后置条件: 链表初始化为空 */
void InitializeList(List * plist);
/*操作: 确定链表是否为空定义,plist指向一个已初始化的链表 */
/*后置条件: 如果链表为空,返回ture;否则返回false */
bool ListIsEmpty(const List * plist);
/*操作: 确定链表是否已满,plist指向一个已初始化的链表 */
/*后置条件: 如果链表已满,返回true;否则返回false */
bool ListIsFull(const List * plist);
/*操作: 确定链表中的项数,plist指向一个已初始化的链表 */
/*后置条件: 返回链表中的项数 */
unsigned int ListItemCount(const List *plist);
/*操作: 在链表的末尾添加项 */
/*前提条件: item是一个待添加至链表的项,plist指向一个已初始化的链表 */
/*后置条件: 如果可以,执行添加操作,返回true;否则返回false */
bool AddItem(Item item, List * plist);
/*操作: 把函数作用于链表的每一项 */
/* plist指向一个已初始化的链表 */
/* pfun指向一个函数,该函数接受一个Item类型参数,无返回值 */
/*后置条件: pfun指向的函数作用于链表的每一项一次 */
void Traverse(const List*plist, void(*pfun)(Item item));
/*操作: 释放已分配的内存(如果有的话) */
/* plist指向一个已初始化的链表 */
/*后置条件: 释放为链表分配的内存,链表设置为空 */
void EmptyTheList(List * plist);
//list.c
#include<stdio.h>
#include<stdlib.h>
#include"list.h"
static void CopyToNode(Item item, Node * pnode);
void InitializeList(List * plist)
{
*plist = NULL;
}
bool ListIsEmpty(const List * plist)
{
if (*plist == NULL)
return true;
else
return false;
}
bool ListIsFull(const List * plist)
{
Node * pt;
bool full;
pt = (Node *)malloc(sizeof(Node));
if (pt == NULL)
full = true;
else
full = false;
free(pt);
return full;
}
unsigned int ListItemCount(const List * plist)
{
unsigned int count = 0;
Node * pnode = *plist;
while (pnode != NULL)
{
++count;
pnode = pnode->next;
}
return count;
}
bool AddItem(Item item, List * plist)
{
Node * pnew;
Node * scan = *plist;
pnew = (Node *)malloc(sizeof(Node));
if (pnew == NULL)
return false;
CopyToNode(item, pnew);
pnew->next = NULL;
if (scan == NULL)
*plist = pnew;
else
{
while (scan->next != NULL)
scan = scan->next;
scan->next = pnew;
}
return true;
}
void Traverse(const List * plist, void(*pfun)(Item item))
{
Node * pnode = *plist;
while (pnode!= NULL)
{
(*pfun)(pnode->item);
pnode = pnode->next;
}
}
void EmptyTheList(List * plist)
{
Node * psave;
while (*plist != NULL)
{
psave = (*plist)->next;
free(*plist);
*plist = psave;
}
}
static void CopyToNode(Item item, Node * pnode)
{
pnode->item = item;
}
// mall.c -- 使用Queue接口
// 和queue.c一起编译
#include <stdio.h>
#include <stdlib.h> // 提供rand()和srand()的原型
#include <time.h> // 提供time()的原型
#include "17_6_queue.h" // 更改Item的typedef
#define MIN_PER_HR 60.0
bool newcustomer(double x); // 是否有新顾客到来?
Item customertime(long when); // 设置顾客参数
int main(void)
{
Queue line; // 新的顾客数据
Item temp; // 模拟的小时数
int hours; // 每小时平均多少位顾客
int perhour; // 每小时平均多少位顾客
long cycle, cyclelimit; // 循环计数器、计数器的上限
long turnaways = 0; // 因队列已满被拒的顾客数量
long customers = 0; // 加入队列的顾客数量
long served = 0; // 在模拟期间咨询过Sigmund的顾客数量
long sum_line = 0; // 累计的队列总长
long wait_time = 0; // 从当前到Sigmund空闲所需的时间
double min_per_cust; // 顾客到来的平均时间
long line_wait = 0; // 队列累计的等待时间
InitializeQueue(&line);
srand((unsigned int)time(0)); // rand()随机初始化
puts("Case Study: Sigmund Lander's Advice Booth");
puts("Enter the number of simulation hours:");
scanf("%d", &hours);
cyclelimit = MIN_PER_HR * hours;
puts("Enter the average number of customers per hour:");
scanf("%d", &perhour);
min_per_cust = MIN_PER_HR / perhour;
for (cycle = 0; cycle < cyclelimit; cycle++)
{
if (newcustomer(min_per_cust))
{
if (QueueIsFull(&line))
turnaways++;
else
{
customers++;
temp = customertime(cycle);
EnQueue(temp, &line);
}
}
if (wait_time <= 0 && !QueueIsEmpty(&line))
{
DeQueue(&temp, &line);
wait_time = temp.processtime;
line_wait += cycle - temp.arrive;
served++;
}
if (wait_time > 0)
wait_time--;
sum_line += QueueItemCount(&line);
}
if (customers > 0)
{
printf("customers accepted: %ld\n", customers);
printf(" customers served: %ld\n", served);
printf(" turnaways: %ld\n", turnaways);
printf("average queue size: %.2f\n", (double)sum_line / cyclelimit);
printf(" average wait time: %.2f minutes\n", (double)line_wait / served);
}
else
puts("No customers!");
EmptyTheQueue(&line);
return 0;
}
// x是顾客到来的平均时间(单位:分钟)
// 如果1分钟内有顾客到来,则返回true
bool newcustomer(double x)
{
if (rand() * x / RAND_MAX < 1)
return true;
else
return false;
}
// when是顾客到来的时间
// 该函数返回一个Item结构,该顾客到达的时间设置为when
// 咨询时间设置为1~3的随机值
Item customertime(long when)
{
Item cust;
cust.processtime = rand() % 3 + 1;
cust.arrive = when;
return cust;
}
// tree.h -- 二叉查找树
// 树种不允许有重复的项
#ifndef _TREE_H_
#define _TREE_H_
#include <stdbool.h>
// 根据具体情况重新定义Item
#define SLEN 20
typedef struct item
{
char petname[SLEN];
char petkind[SLEN];
} Item;
#define MAXITEMS 10
typedef struct trnode
{
Item item;
struct trnode *left; // 指向左分支的指针
struct trnode *right; // 指向右分支的指针
} Trnode;
typedef struct tree
{
Trnode *root; // 指向根节点的指针
int size; // 树的项数
} Tree;
// 函数原型
// 操作: 把树初始化为空
// 前提条件: ptree指向一个树
// 后置条件: 树被初始化为空
void InitializeTree(Tree *ptree);
// 操作: 确定树是否为空
// 前提条件: ptree指向一个树
// 后置条件: 如果树为空,该函数返回true,否则返回false
bool TreeIsEmpty(const Tree *ptree);
// 操作: 确定树是否已满
// 前提条件: ptree指向一个树
// 后置条件: 如果树已满,该函数返回true,否则返回false
bool TreeIsFull(const Tree *ptree);
// 操作: 确定树的项数
// 前提条件: ptree指向一个树
// 后置条件: 返回树的项数
int TreeItemCount(const Tree *ptree);
// 操作: 在树中添加一个项
// 前提条件: pi是待添加项的地址,ptree指向一个一初始化的树
// 后置条件: 如果可以添加,该函数将在树中添加一个项并返回true,否则返回false
bool AddItem(const Item *pi, Tree *ptree);
// 操作: 在树中查找一个项
// 前提条件: pi指向一个项,ptree指向一个已初始化的树
// 后置条件: 如果在树中添加一个项,该函数返回true,否则返回false
bool InTree(const Item *pi, const Tree *ptree);
// 操作: 从树中删除一个项
// 前提条件: pi是删除项的地址,ptree指向一个已初始化的树
// 后置条件: 如果从树中成功删除一格项,该函数返回true,否则返回false
bool DeleteItem(const Item *pi, Tree *ptree);
// 操作: 把函数应用到树中的每一项
// 前提条件: ptree指向一个树,pfun指向一个函数,该函数接收一个Item类型的参数,并无返回值
// 后置条件: pfun咋想的这个函数为树中的每一项执行一次
void Traverse(const Tree *ptree, void (*pfun)(Item item));
// 操作: 删除树中的所有内容
// 前提条件: ptree指向一个已初始化的树
// 后置条件: 树为空
void DeleteAll(Tree *ptree);
// tree.c -- 树的支持函数
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include "17_10_tree.h"
// 局部数据类型
typedef struct pair
{
Trnode *parent;
Trnode *child;
} Pair;
// 局部函数的原型
static Trnode *MakeNode(const Item *pi);
static bool ToLeft(const Item *i1, const Item *i2);
static bool ToRight(const Item *i1, const Item *i2);
static void AddNode(Trnode *new_node, Trnode *root);
static void InOrder(const Trnode *root, void (*pfun)(Item item));
static Pair SeekItem(const Item *pi, const Tree *ptree);
static void DeleteNode(Trnode **ptr);
static void DeleteAllNodes(Trnode *ptr);
// 函数定义
void InitializeTree(Tree *ptree)
{
ptree->root = NULL;
ptree->size = 0;
}
bool TreeIsEmpty(const Tree *ptree)
{
if (ptree->root == NULL)
return true;
else
return false;
}
bool TreeIsFull(const Tree *ptree)
{
if (ptree->root == NULL)
return true;
else
return false;
}
int TreeItemCount(const Tree *ptree)
{
if (ptree->size == MAXITEMS)
return true;
else
return false;
}
bool AddItem(const Item *pi, Tree *ptree)
{
Trnode *new_node;
if (TreeIsFull(ptree))
{
fprintf(stderr, "Tree is full\n");
return false; // 提前返回
}
if (SeekItem(pi, ptree).child != NULL)
{
fprintf(stderr, "Attempted to add duplicate item\n");
return false; // 提前返回
}
new_node = MakeNode(pi); // 指向新节点
if (new_node == NULL)
{
fprintf(stderr, "Couldn't create node\n");
return false; // 提前返回
}
// 成功创建了一个新节点
ptree->size++;
if (ptree->root == NULL) // 情况1:树为空
ptree->root = new_node; // 新节点为树的根节点
else // 情况2:树不为空
AddNode(new_node, ptree->root); // 在树中添加新节点
return true; // 成功返回
}
bool InTree(const Item *pi, const Tree *ptree)
{
return (SeekItem(pi, ptree).child == NULL) ? false : true;
}
bool DeleteItem(const Item *pi, Tree *ptree)
{
Pair look;
look = SeekItem(pi, ptree);
if (look.child == NULL)
return false;
if (look.parent == NULL) // 删除根节点项
DeleteNode(&ptree->root);
else if (look.parent->left == look.child)
DeleteNode(&look.parent->left);
else
DeleteNode(&look.parent->right);
ptree->size--;
return true;
}
void Traverse(const Tree *ptree, void (*pfun)(Item item))
{
if (ptree != NULL)
InOrder(ptree->root, pfun);
}
void DeleteAll(Tree *ptree)
{
if (ptree != NULL)
DeleteAllNodes(ptree->root);
ptree->root = NULL;
ptree->size = 0;
}
// 局部函数
static void InOrder(const Trnode *root, void (*pfun)(Item item))
{
if (root != NULL)
{
InOrder(root->left, pfun);
(*pfun)(root->item);
InOrder(root->right, pfun);
}
}
static void DeleteAllNodes(Trnode *root)
{
Trnode *pright;
if (root != NULL)
{
pright = root->right;
DeleteAllNodes(root->left);
free(root);
DeleteAllNodes(pright);
}
}
static void AddNode(Trnode *new_node, Trnode *root)
{
if (ToLeft(&new_node->item, &root->item))
{
if (root->left == NULL) // 空子树
root->left = new_node; // 把结点添加到此处
else
AddNode(new_node, root->left); // 否则处理该子树
}
else if (ToRight(&new_node->item, &root->item))
{
if (root->right == NULL) // 空子树
root->right = new_node; // 把结点添加到此处
else
AddNode(new_node, root->right); // 否则处理该子树
}
else // 不允许有重复项
{
fprintf(stderr, "location error in AddNode()\n");
exit(1);
}
}
static bool ToLeft(const Item *i1, const Item *i2)
{
int comp1;
if ((comp1 = strcmp(i1->petname, i2->petname)) < 0)
return true;
else if (comp1 == 0 && strcmp(i1->petkind, i2->petkind) < 0)
return true;
else
return false;
}
static bool ToRight(const Item *i1, const Item *i2)
{
int comp1;
if ((comp1 = strcmp(i1->petname, i2->petname)) > 0)
return true;
else if (comp1 == 0 && strcmp(i1->petkind, i2->petkind) > 0)
return true;
else
return false;
}
static Trnode *MakeNode(const Item *pi)
{
Trnode *new_node;
new_node = (Trnode *)malloc(sizeof(Trnode));
if (new_node != NULL)
{
new_node->item = *pi;
new_node->left = NULL;
new_node->right = NULL;
}
return new_node;
}
static Pair SeekItem(const Item *pi, const Tree *ptree)
{
Pair look;
look.parent = NULL;
look.child = ptree->root;
if (look.child == NULL)
return look; // 提前返回
while (look.child == NULL)
{
if (ToLeft(pi, &(look.child->item)))
{
look.parent = look.child;
look.child = look.child->left;
}
else if (ToRight(pi, &(look.child->item)))
{
look.parent = look.child;
look.child = look.child->right;
}
else // 如果前两种情况都不满足,则必定是相等的情况
break; // look.child目标项的结点
}
return look; // 成功返回
}
static void DeleteNode(Trnode **ptr) // ptr是指向目标节点的父节点指针成员的地址
{
Trnode *temp;
if ((*ptr)->left == NULL)
{
temp = *ptr;
*ptr = (*ptr)->right;
free(temp);
}
else if ((*ptr)->right == NULL)
{
temp = *ptr;
*ptr = (*ptr)->left;
free(temp);
}
else // 被删除的结点有两个子节点
{
// 找到重新连接右子树的位置
for (temp = (*ptr)->left; temp->right != NULL; temp = temp->right)
continue;
temp->right = (*ptr)->right;
temp = *ptr;
*ptr = (*ptr)->left;
free(temp);
}
}