C语言作为一门高效的编程语言,其强大的功能部分归功于其标准库(Standard Library)提供的丰富函数集合。这些库函数帮助开发者轻松完成输入输出、字符串处理、字符处理、数学运算、内存管理、时间日期处理、文件操作以及进程控制等任务。下面,我们将逐一详细介绍这些方面的常用库函数。
1. 输入输出函数
2. scanf()
函数原型:
int scanf(const char *format, ...);
功能: scanf()
函数从标准输入(通常是键盘)读取并格式化输入。它根据提供的格式字符串 format
来解析输入,并将结果存储在后续参数指定的变量中。
用法示例:
#include <stdio.h> | |
int main() { | |
int age; | |
float height; | |
printf("Enter your age and height: "); | |
scanf("%d %f", &age, &height); | |
printf("You are %d years old and %.2f meters tall.\n", age, height); | |
return 0; | |
} |
注意:
3. puts()
函数原型
int puts(const char *s);
功能: puts()
函数输出一个字符串到标准输出,并在末尾自动添加换行符。它不接受格式字符串,只是简单地输出提供的字符串和一个换行符。
用法示例:
#include <stdio.h> | |
int main() { | |
puts("Hello, World!"); | |
return 0; | |
} |
注意:
4. gets()
(不推荐使用)
函数原型(尽管不推荐,但了解其基本形式):
char *gets(char *s);
功能(尽管不推荐): gets()
函数从标准输入读取一行直到换行符,但不包括换行符本身,并将读取的字符串(包括终止的空字符 \0
)存储在提供的字符数组中。然而,由于它无法防止缓冲区溢出,因此已被弃用并从许多现代C标准库中移除。
不推荐使用的原因:
替代方案:
5. putchar()
函数原型:
int putchar(int char);
功能: putchar()
函数输出一个字符到标准输出。尽管其参数是 int
类型,但它只输出该整数的低8位(即ASCII码值对应的字符)。
用法示例:
#include <stdio.h> | |
int main() { | |
putchar('A'); | |
putchar('\n'); // 输出换行符 | |
return 0; | |
} |
6. getchar()
函数原型:
int getchar(void);
功能: getchar()
函数从标准输入读取下一个字符(等待用户输入),并将其作为无符号字符(但通常作为 int
返回,以便区分EOF)返回。
用法示例:
#include <stdio.h> | |
int main() { | |
char c; | |
c = getchar(); // 读取一个字符 | |
putchar(c); // 输出该字符 | |
putchar('\n'); // 输出换行符 | |
return 0; | |
} |
注意:
注意事项
示例:使用 fgets()
#include <stdio.h> | |
int main() { | |
char buffer[100]; | |
printf("Enter a line of text: "); | |
if (fgets(buffer, sizeof(buffer), stdin) != NULL) { | |
// fgets 会读取换行符(如果缓冲区空间允许)并存储在字符串中 | |
// 可以选择移除字符串末尾的换行符 | |
size_t len = strlen(buffer); | |
if (len > 0 && buffer[len - 1] == '\n') { | |
buffer[len - 1] = '\0'; // 移除换行符 | |
} | |
printf("You entered: %s\n", buffer); | |
} else { | |
// fgets 返回 NULL 通常意味着发生了错误或到达了文件末尾(对于 stdin 来说不太可能) | |
printf("An error occurred or EOF reached.\n"); | |
} | |
return 0; | |
} |
在这个示例中,fgets()
被用来安全地读取一行文本,并通过检查字符串末尾是否包含换行符来决定是否移除它。这是处理用户输入时的一个常见需求。
printf()
:用于向标准输出(通常是屏幕)打印格式化的字符串。scanf()
:从标准输入(通常是键盘)读取并格式化输入。puts()
:输出一个字符串到标准输出,并在末尾自动添加换行符。gets()
(不推荐使用,存在安全风险):从标准输入读取一行直到换行符,但不包括换行符本身。putchar()
:输出一个字符到标准输出。getchar()
:从标准输入读取下一个字符。输入输出函数的详解及用法
1.
printf()
函数原型:
int printf(const char *format, ...);
功能:
printf()
函数用于向标准输出(通常是屏幕)打印格式化的字符串。它根据提供的格式字符串format
来格式化后续参数,并将结果输出到标准输出。用法示例:
#include <stdio.h> int main() { int number = 10; float pi = 3.14159; printf("The number is %d and pi is approximately %.2f.\n", number, pi); return 0; } 注意:
%d
用于整数。%.2f
用于浮点数,保留两位小数。\n
是换行符。- 变量前的
&
是取地址符,因为scanf()
需要变量的地址来存储输入的数据。 - 输入时,数据之间可以有空格,但格式字符串中的空格用于匹配输入中的空白字符(如空格、制表符或换行符)。
- 输出的字符串末尾会自动添加一个换行符。
- 缓冲区溢出风险:如果输入的字符串超过了目标数组的大小,
gets()
会继续读取并覆盖数组边界之外的内存,这可能导致程序崩溃或安全漏洞。 - 使用
fgets()
,它允许指定最大读取长度,从而防止缓冲区溢出。 - 当输入到达文件末尾(EOF)时,
getchar()
会返回EOF
,这是一个在<stdio.h>
中定义的宏,通常等于-1
。但是,因为getchar()
的返回类型是int
而不是char
,所以它可以表示所有可能的字符值以及 EOF。 -
EOF:在文件结束或发生读取错误时,
getchar()
会返回 EOF。为了区分正常的字符和 EOF,通常将getchar()
的结果存储在一个int
类型的变量中,然后检查该变量是否等于 EOF。 -
缓冲区:标准输入(stdin)是缓冲的,这意味着在读取下一个字符之前,可能会先在内部缓冲区中累积一些输入。这通常对程序是透明的,但在某些情况下(如混合使用
scanf()
和getchar()
时),可能会遇到意外行为,因为scanf()
可能会在读取后留下换行符或其他字符在缓冲区中。 -
替代
gets()
:如上所述,gets()
函数因为安全原因已被弃用。应使用fgets()
来替代,fgets()
函数可以从指定的流中读取一行,并将结果存储在字符串中,直到遇到换行符、文件结束符 EOF 或已读取了n-1
个字符为止(n
是指定的最大读取长度),并在字符串末尾添加一个空字符\0
。
2. 字符串处理
strcpy()
:复制字符串,包括结束符\0
。strcat()
:连接两个字符串。strlen()
:计算字符串的长度,不包括结束符\0
。strcmp()
:比较两个字符串,按字典顺序。strncpy()
:复制指定长度的字符串,确保目标字符串以\0
结尾。strstr()
:查找子串在字符串中首次出现的位置。sprintf()
:将格式化的数据写入字符串。字符串处理函数的详解及用法
1.
strcpy()
函数原型:
char *strcpy(char *dest, const char *src);
功能:
strcpy()
函数用于复制字符串src
(包括其结尾的空字符\0
)到字符串dest
所指向的数组中。注意,dest
必须有足够的空间来存放要复制的字符串。用法示例:
#include <stdio.h> #include <string.h> int main() { char src[] = "Hello, World!"; char dest[50]; // 确保有足够的空间 strcpy(dest, src); printf("Copied string: %s\n", dest); return 0; } 2.
strcat()
函数原型:
char *strcat(char *dest, const char *src);
功能:
strcat()
函数将字符串src
连接到字符串dest
的末尾,并包括src
的结尾空字符\0
。同样,dest
必须有足够的空间来存放两个字符串连接后的结果。用法示例:
#include <stdio.h> #include <string.h> int main() { char dest[50] = "Hello, "; const char *src = "World!"; strcat(dest, src); printf("Concatenated string: %s\n", dest); return 0; } 3.
strlen()
函数原型:
size_t strlen(const char *str);
功能:
strlen()
函数计算并返回给定字符串str
的长度,不包括结尾的空字符\0
。用法示例:
#include <stdio.h> #include <string.h> int main() { const char *str = "Hello, World!"; size_t len = strlen(str); printf("Length of the string: %zu\n", len); return 0; } 4.
strcmp()
函数原型:
int strcmp(const char *str1, const char *str2);
功能:
strcmp()
函数比较两个字符串str1
和str2
。如果str1
和str2
字符串相等,则返回 0;如果str1
在字典序上小于str2
,则返回负值;如果str1
在字典序上大于str2
,则返回正值。用法示例:
#include <stdio.h> #include <string.h> int main() { const char *str1 = "apple"; const char *str2 = "banana"; int result = strcmp(str1, str2); if (result < 0) { printf("\"%s\" is less than \"%s\"\n", str1, str2); } else if (result > 0) { printf("\"%s\" is greater than \"%s\"\n", str1, str2); } else { printf("\"%s\" is equal to \"%s\"\n", str1, str2); } return 0; } 5.
strncpy()
函数原型:
char *strncpy(char *dest, const char *src, size_t n);
功能:
strncpy()
函数将字符串src
的前n
个字符复制到dest
指向的数组中(不会复制src
的结尾空字符\0
,除非它是被复制的n
个字符之一)。如果src
的长度小于n
,则结果字符串将用空字符\0
填充直到总共复制了n
个字符。用法示例:
#include <stdio.h> #include <string.h> int main() { char dest[20]; const char *src = "Hello, World!"; strncpy(dest, src, 5); dest[5] = '\0'; // 确保字符串以 '\0' 结尾 printf("Copied string with strncpy: %s\n", dest); return 0; } 注意:在上面的示例中,我手动添加了
'\0'
来确保dest
是一个有效的字符串。在使用strncpy()
时,这是一个常见的做法,因为strncpy()
不会总是自动添加结尾的空字符\0
(如果源字符串的长度小于指定的n
个字符,则不会添加额外的\0
)。6.
strstr()
函数原型:
char *strstr(const char *str, const char *substr);
功能:
strstr()
函数在字符串str
中查找第一次出现子字符串substr
的位置,并返回从该位置开始到str
结尾的所有字符的指针。如果没有找到子字符串,则返回NULL
。用法示例:
#include <stdio.h> #include <string.h> int main() { const char *str = "Hello, World!"; const char *substr = "World"; char *found = strstr(str, substr); if (found) { printf("Substring found: %s\n", found); } else { printf("Substring not found.\n"); } return 0; } 7.
sprintf()
函数原型:
int sprintf(char *str, const char *format, ...);
功能:
sprintf()
函数根据指定的格式format
来格式化数据,并将结果写入字符串str
中。它可以接受多个参数,这些参数将根据format
指定的格式被转换并插入到结果字符串中。用法示例:
#include <stdio.h> int main() { char buffer[50]; sprintf(buffer, "The answer is %d", 42); printf("Formatted string: %s\n", buffer); return 0; } sprintf()
是非常强大的,但使用时需要注意缓冲区溢出的风险,特别是当目标字符串str
的大小不足以容纳格式化后的字符串时。
3. 字符处理
用法示例:
#include <stdio.h> | |
#include <ctype.h> | |
int main() { | |
char ch1 = 'A'; | |
char ch2 = 'a'; | |
char ch3 = '5'; | |
if (isupper(ch1)) { | |
printf("%c is an uppercase letter.\n", ch1); | |
} else { | |
printf("%c is not an uppercase letter.\n", ch1); | |
} | |
if (islower(ch2)) { | |
printf("%c is a lowercase letter.\n", ch2); | |
} else { | |
printf("%c is not a lowercase letter.\n", ch2); | |
} | |
// 注意:非字母字符不会返回true | |
if (isupper(ch3)) { | |
printf("%c is an uppercase letter.\n", ch3); | |
} else { | |
printf("%c is not an uppercase letter.\n", ch3); | |
} | |
if (islower(ch3)) { | |
printf("%c is a lowercase letter.\n", ch3); | |
} else { | |
printf("%c is not a lowercase letter.\n", ch3); | |
} | |
return 0; | |
} |
注意:
tolower()
:将大写字母转换为小写字母。toupper()
:将小写字母转换为大写字母。isalpha()
:检查字符是否为字母。isdigit()
:检查字符是否为数字。isspace()
:检查字符是否为空白字符(如空格、制表符等)。isupper()
和islower()
:分别检查字符是否为大写或小写字母。字符处理函数的详解及用法
1.
tolower()
函数原型:
int tolower(int c);
功能: 将大写字母转换为小写字母。如果参数
c
不是大写字母,则不改变其值。用法示例:
#include <stdio.h> #include <ctype.h> int main() { char ch = 'A'; printf("Original: %c, Lowercase: %c\n", ch, tolower(ch)); // 注意:非字母字符不受影响 ch = '*'; printf("Original: %c, Lowercase: %c\n", ch, tolower(ch)); return 0; } 2.
toupper()
函数原型:
int toupper(int c);
功能: 将小写字母转换为大写字母。如果参数
c
不是小写字母,则不改变其值。用法示例:
#include <stdio.h> #include <ctype.h> int main() { char ch = 'a'; printf("Original: %c, Uppercase: %c\n", ch, toupper(ch)); // 注意:非字母字符不受影响 ch = '1'; printf("Original: %c, Uppercase: %c\n", ch, toupper(ch)); return 0; } 3.
isalpha()
函数原型:
int isalpha(int c);
功能: 检查参数
c
是否为字母(大写或小写)。用法示例:
#include <stdio.h> #include <ctype.h> int main() { char ch1 = 'a'; char ch2 = '3'; if (isalpha(ch1)) { printf("%c is an alphabet.\n", ch1); } else { printf("%c is not an alphabet.\n", ch1); } if (isalpha(ch2)) { printf("%c is an alphabet.\n", ch2); } else { printf("%c is not an alphabet.\n", ch2); } return 0; } 4.
isdigit()
函数原型:
int isdigit(int c);
功能: 检查参数
c
是否为数字(0-9)。用法示例:
#include <stdio.h> #include <ctype.h> int main() { char ch1 = '5'; char ch2 = 'a'; if (isdigit(ch1)) { printf("%c is a digit.\n", ch1); } else { printf("%c is not a digit.\n", ch1); } if (isdigit(ch2)) { printf("%c is a digit.\n", ch2); } else { printf("%c is not a digit.\n", ch2); } return 0; } 5.
isspace()
函数原型:
int isspace(int c);
功能: 检查参数
c
是否为空白字符(如空格、制表符、换行符等)。用法示例:
#include <stdio.h> #include <ctype.h> int main() { char ch1 = ' '; char ch2 = 'a'; if (isspace(ch1)) { printf("%c is a whitespace character.\n", ch1); } else { printf("%c is not a whitespace character.\n", ch1); } if (isspace(ch2)) { printf("%c is a whitespace character.\n", ch2); } else { printf("%c is not a whitespace character.\n", ch2); } return 0; } 6.
isupper()
和islower()
函数原型:
int isupper(int c); int islower(int c); 功能:
isupper()
:检查参数c
是否为大写字母。islower()
:检查参数c
是否为小写字母。- 这些函数接受一个
int
类型的参数,但是实际上这个参数应该是一个字符(即char
类型,通常会被自动提升为int
)。这是因为函数的实现需要区分EOF(文件结束标志)和有效的字符,而EOF通常被定义为-1,这在char
类型中无法直接表示。 - 如果传递给这些函数的参数不是有效的单字节字符(例如,在多字节字符集环境中使用),则结果可能是未定义的。在处理国际化字符时,应该使用适当的国际化库函数。
- 这些函数都是针对C语言设计的,但在其他支持类似C库的环境中(如C++,尽管C++有自己的字符串和字符处理方式),这些函数通常也是可用的,或者通过适当的头文件(如
<cctype>
在C++中)提供。
4. 数学运算
用法示例:
#include <stdio.h> | |
#include <math.h> | |
int main() { | |
double angleRadians = M_PI / 4; | |
printf("sin(%.2f radians) = %.2f\n", angleRadians, sin(angleRadians)); | |
printf("cos(%.2f radians) = %.2f\n", angleRadians, cos(angleRadians)); | |
printf("tan(%.2f radians) = %.2f\n", angleRadians, tan(angleRadians)); | |
return 0; | |
} |
asin(), acos(), atan()
函数原型:
double asin(double x); | |
double acos(double x); | |
double atan(double x); |
功能:
用法示例:
#include <stdio.h> | |
#include <math.h> | |
int main() { | |
double sinValue = 0.5; | |
double resultRadians = asin(sinValue); | |
double degrees = resultRadians * (180.0 / M_PI); | |
printf("asin(%.2f) = %.2f radians (%.2f degrees)\n", sinValue, resultRadians, degrees); | |
// 类似地,可以使用acos和atan | |
return 0; | |
} |
ceil() 和 floor()
函数原型:
double ceil(double x); | |
double floor(double x); |
功能:
ceil()
函数原型:
double ceil(double x);
功能: ceil()
函数将x
向上取整到最接近的整数。如果x
是一个整数,则返回其本身;如果x
是正数且非整数,则返回大于x
的最小整数;如果x
是负数,则返回小于或等于x
的最大整数(注意,这仍然是向下取整到最接近的整数,但在数轴上方向是向上的,因为整数集是离散的)。
用法示例:
#include <stdio.h> | |
#include <math.h> | |
int main() { | |
double num1 = 3.2; | |
double num2 = -3.8; | |
double result1 = ceil(num1); | |
double result2 = ceil(num2); | |
printf("ceil(%.2f) = %.0f\n", num1, result1); | |
printf("ceil(%.2f) = %.0f\n", num2, result2); | |
return 0; | |
} |
输出:
ceil(3.20) = 4
ceil(-3.80) = -3
floor()
函数原型:
double floor(double x);
功能: floor()
函数将x
向下取整到最接近的整数。如果x
是一个整数,则返回其本身;如果x
是正数且非整数,则返回小于x
的最大整数;如果x
是负数,则返回小于x
的最大整数(这实际上是直接向下取整)。
用法示例:
#include <stdio.h> | |
#include <math.h> | |
int main() { | |
double num1 = 3.2; | |
double num2 = -3.8; | |
double result1 = floor(num1); | |
double result2 = floor(num2); | |
printf("floor(%.2f) = %.0f\n", num1, result1); | |
printf("floor(%.2f) = %.0f\n", num2, result2); | |
return 0; | |
} |
输出:
floor(3.20) = 3
floor(-3.80) = -4
round()
函数原型: 虽然C标准库中没有直接命名为round()
的函数来执行四舍五入到最接近的整数,但大多数C实现(如GNU C Library, glibc)提供了这样的函数。
double round(double x);
功能: round()
函数将x
四舍五入到最接近的整数。如果x
恰好位于两个整数的中间,则结果将四舍五入到偶数。这是所谓的“银行家舍入法”。
用法示例:
#include <stdio.h> | |
#include <math.h> | |
int main() { | |
double num1 = 3.5; | |
double num2 = 4.5; | |
double num3 = 2.3; | |
double num4 = -2.3; | |
double result1 = round(num1); | |
double result2 = round(num2); | |
double result3 = round(num3); | |
double result4 = round(num4); | |
printf("round(%.2f) = %.0f\n", num1, result1); | |
printf("round(%.2f) = %.0f\n", num2, result2); | |
printf("round(%.2f) = %.0f\n", num3, result3); | |
printf("round(%.2f) = %.0f\n", num4, result4); | |
return 0; | |
} |
输出(注意银行家舍入法):
round(3.50) = 4
round(4.50) = 4
round(2.30) = 2
round(-2.30) = -2
请注意,round()
函数返回的是double
类型,但通常四舍五入的结果是一个整数,因此可以直接赋值给int
类型的变量(如果需要的话,并且不介意潜在的类型转换)。然而,在某些情况下(如恰好位于两个整数中间时),结果可能仍然是一个double
类型的浮点数。
sqrt()
:计算平方根。pow()
:计算x的y次幂。fabs()
:计算浮点数的绝对值。sin()
、cos()
、tan()
:分别计算正弦、余弦、正切值。asin()
、acos()
、atan()
:分别计算反正弦、反余弦、反正切值。ceil()
和floor()
:分别向上或向下取整到最接近的整数。round()
:四舍五入到最接近的整数。sqrt()
函数原型:
double sqrt(double x);
功能:计算并返回参数
x
的平方根。如果x
是负数,则结果是一个NaN(Not a Number)值。用法示例:
#include <stdio.h> #include <math.h> int main() { double num = 16.0; double result = sqrt(num); printf("The square root of %.2f is %.2f\n", num, result); return 0; } pow()
函数原型:
double pow(double x, double y);
功能:计算并返回
x
的y
次幂(x^y
)。用法示例:
#include <stdio.h> #include <math.h> int main() { double base = 2.0; double exponent = 3.0; double result = pow(base, exponent); printf("%.0f to the power of %.0f is %.0f\n", base, exponent, result); return 0; } fabs()
函数原型:
double fabs(double x);
功能:计算并返回浮点数
x
的绝对值。用法示例:
#include <stdio.h> #include <math.h> int main() { double num = -123.45; double absNum = fabs(num); printf("The absolute value of %.2f is %.2f\n", num, absNum); return 0; } sin(), cos(), tan()
函数原型:
double sin(double x); double cos(double x); double tan(double x); 功能:
sin(x)
:计算x
(以弧度为单位)的正弦值。cos(x)
:计算x
(以弧度为单位)的余弦值。tan(x)
:计算x
(以弧度为单位)的正切值。asin(x)
:计算x
的反正弦值,返回的角度在[-π/2, π/2]
范围内,以弧度为单位。acos(x)
:计算x
的反余弦值,返回的角度在[0, π]
范围内,以弧度为单位。atan(x)
:计算x
的反正切值,返回的角度在[-π/2, π/2]
范围内,以弧度为单位。ceil(x)
:将x
向上取整到最接近的整数。当然,以下是对ceil()
和floor()
函数以及round()
函数的进一步详解及用法:
5. 内存管理
malloc()
:动态分配指定大小的内存块。calloc()
:分配内存并清零,常用于分配数组。realloc()
:调整之前通过malloc
或calloc
分配的内存块大小。free()
:释放之前分配的内存块。zmalloc()
函数原型:
void* malloc(size_t size);
功能:动态分配指定大小的内存块,并返回一个指向该内存块的指针。如果分配失败,则返回
NULL
。用法示例:
#include <stdio.h> #include <stdlib.h> int main() { int *ptr; int n = 5; // 假设我们需要5个整数的空间 ptr = (int*)malloc(n * sizeof(int)); // 分配内存 if (ptr == NULL) { printf("Memory allocation failed!\n"); return 1; } // 使用ptr指向的内存 for(int i = 0; i < n; i++) { ptr[i] = i * i; } // 打印分配的内存中的值 for(int i = 0; i < n; i++) { printf("%d ", ptr[i]); } // 释放内存 free(ptr); return 0; } calloc()
函数原型:
void* calloc(size_t num, size_t size);
功能:分配一块足够存储
num
个size
大小的元素的内存,并将所有位初始化为0。返回一个指向分配的内存的指针;如果分配失败,则返回NULL
。用法示例:
#include <stdio.h> #include <stdlib.h> int main() { int *ptr; int n = 5; // 假设我们需要5个整数的空间 ptr = (int*)calloc(n, sizeof(int)); // 分配并初始化内存 if (ptr == NULL) { printf("Memory allocation failed!\n"); return 1; } // 使用ptr指向的内存(注意:calloc已经初始化为0) for(int i = 0; i < n; i++) { ptr[i] = i + 100; // 示例赋值 } // 打印分配的内存中的值 for(int i = 0; i < n; i++) { printf("%d ", ptr[i]); } // 释放内存 free(ptr); return 0; } realloc()
函数原型:
void* realloc(void* ptr, size_t size);
功能:调整之前通过
malloc
、calloc
或realloc
分配的内存块的大小。如果ptr
是NULL
,则realloc
的行为与malloc
相同。如果调整成功,返回指向新内存块的指针(可能与原指针相同,也可能不同)。如果失败,则返回NULL
,但原内存块不会被释放。用法示例:
#include <stdio.h> #include <stdlib.h> int main() { int *ptr; int n = 5; ptr = (int*)malloc(n * sizeof(int)); if (ptr == NULL) { printf("Initial memory allocation failed!\n"); return 1; } // 假设我们需要更多空间 n = 10; ptr = (int*)realloc(ptr, n * sizeof(int)); if (ptr == NULL) { printf("Memory reallocation failed!\n"); free(ptr); // 注意:这里的free调用在逻辑上是多余的,因为ptr已经是NULL了 return 1; } // 使用ptr指向的新内存... // 释放内存 free(ptr); return 0; } free()
函数原型:
void free(void* ptr);
功能:释放之前通过
malloc
、calloc
或realloc
分配的内存块。如果ptr
是NULL
,则free
函数什么也不做。注意:释放内存后,应将指向该内存的指针设置为
NULL
,以避免野指针(dangling pointer)的问题。野指针是指向已经被释放的内存的指针,如果尝试通过野指针访问或修改内存,将导致未定义行为,可能引发程序崩溃或数据损坏。用法示例(接上面
realloc
的示例):#include <stdio.h> #include <stdlib.h> int main() { int *ptr; int n = 5; ptr = (int*)malloc(n * sizeof(int)); if (ptr == NULL) { printf("Initial memory allocation failed!\n"); return 1; } // 假设我们需要更多空间 n = 10; ptr = (int*)realloc(ptr, n * sizeof(int)); if (ptr == NULL) { printf("Memory reallocation failed!\n"); // 注意:如果realloc失败,这里的原始ptr可能仍然是有效的(如果realloc内部决定不移动内存块), // 但因为我们不知道这一点,所以最好总是检查realloc的返回值。 // 不过,如果我们确定要退出,可以安全地跳过这里的free调用(因为程序即将退出), // 或者使用原始的ptr调用free(如果确信realloc没有移动内存块)。 // 这里为了演示,我们假设退出。 return 1; } // 使用ptr指向的新内存... // 释放内存 free(ptr); // 将指针设置为NULL,避免野指针 ptr = NULL; // 现在ptr是安全的,不会指向任何已分配的内存 return 0; } 在这个示例中,我们在释放内存后立即将
ptr
设置为NULL
。这是一个好习惯,可以帮助避免在程序的其他部分意外使用到这个已经被释放的指针。记住,free()
函数只是释放了内存,让操作系统知道这块内存可以被再次使用,但它不会自动将指针设置为NULL
。因此,程序员需要负责将不再使用的指针设置为NULL
。
6. 时间日期
time()
:获取当前时间(自1970年1月1日以来的秒数)。localtime()
和gmtime()
:将time_t
值转换为本地时间或格林威治时间。strftime()
:将时间格式化为字符串。difftime()
:计算两个时间点之间的差异(以秒为单位)。time()
函数原型:
#include <time.h> time_t time(time_t *tloc); 功能:获取当前时间(自1970年1月1日(称为Unix纪元或Epoch)以来的秒数),并可选地将其存储在提供的
time_t
类型的变量中。用法示例:
#include <stdio.h> #include <time.h> int main() { time_t rawtime; time(&rawtime); printf("Current time since Epoch: %ld\n", rawtime); return 0; } localtime() 和 gmtime()
函数原型:
#include <time.h> struct tm *localtime(const time_t *timep); struct tm *gmtime(const time_t *timep); 功能:这两个函数都将
time_t
值(自Epoch以来的秒数)转换为struct tm
类型的结构体,其中包含了详细的日期和时间信息(如年、月、日、小时、分钟、秒等)。localtime()
将时间转换为本地时区的时间,而gmtime()
则将其转换为UTC(格林威治时间)。用法示例:
#include <stdio.h> #include <time.h> int main() { time_t rawtime; struct tm * timeinfo; time(&rawtime); timeinfo = localtime(&rawtime); printf("Local time and date: %s", asctime(timeinfo)); timeinfo = gmtime(&rawtime); printf("UTC time and date: %s", asctime(timeinfo)); return 0; } 注意:这里使用了
asctime()
函数来将struct tm
结构体转换为易读的字符串形式。strftime()
函数原型:
#include <time.h> size_t strftime(char *s, size_t max, const char *format, const struct tm *tm); 功能:根据提供的格式字符串
format
,将struct tm
类型的时间信息格式化为字符串。用法示例:
#include <stdio.h> #include <time.h> int main() { time_t rawtime; struct tm * timeinfo; char buffer[80]; time(&rawtime); timeinfo = localtime(&rawtime); strftime(buffer, 80, "%Y-%m-%d %H:%M:%S", timeinfo); printf("Formatted date and time: %s\n", buffer); return 0; } difftime()
函数原型:
#include <time.h> double difftime(time_t time2, time_t time1); 功能:计算两个时间点(
time1
和time2
)之间的差异,以秒为单位。返回值是time2 - time1
的结果,类型为double
。用法示例:
#include <stdio.h> #include <time.h> int main() { time_t start, end; double elapsed_time; time(&start); // 假设这里有一些耗时的操作 // ... time(&end); elapsed_time = difftime(end, start); printf("Elapsed time: %.2f seconds\n", elapsed_time); return 0; } 这些函数共同提供了在C程序中处理和格式化时间的基本能力。
7. 文件操作
示例:使用rename()
函数
以下是一个简单的示例,展示了如何使用rename()
函数在C语言中重命名文件:
#include <stdio.h> | |
int main() { | |
const char *oldname = "oldfile.txt"; | |
const char *newname = "newfile.txt"; | |
if (rename(oldname, newname) == 0) { | |
printf("File successfully renamed\n"); | |
} else { | |
perror("Error renaming file"); | |
return 1; | |
} | |
return 0; | |
} |
请注意,如果oldname
指定的文件不存在,或者由于某些原因(如权限问题)无法重命名该文件,rename()
函数将失败,并设置errno
以指示错误原因。您可以使用perror()
函数打印出与errno
相关联的错误消息。
另外,请注意,在某些实现中,如果newname
已经存在,并且它是一个文件而不是目录,则rename()
函数可能会覆盖它,而不会给出错误。但是,这种行为并不是所有系统都保证的,因此在跨平台编程时应该谨慎处理这种情况。如果可能的话,最好在尝试重命名之前检查目标名称是否已经存在
fopen()
:打开文件。fclose()
:关闭文件。fread()
和fwrite()
:从文件读取或向文件写入数据块。fseek()
:移动文件指针到指定位置。ftell()
:返回文件指针的当前位置。remove()
:删除文件。rename()
:重命名文件或目录。fopen()
函数原型:
#include <stdio.h> FILE *fopen(const char *filename, const char *mode); 功能:打开文件,并返回一个指向
FILE
对象的指针,该对象用于后续的文件操作。filename
是文件路径和名称,mode
是打开文件的模式(如只读、只写、追加等)。用法示例:
FILE *fp = fopen("example.txt", "r"); // 以只读模式打开文件 if (fp == NULL) { perror("Error opening file"); return(-1); } // 后续文件操作... fclose(fp); // 操作完成后关闭文件 fclose()
函数原型:
#include <stdio.h> int fclose(FILE *stream); 功能:关闭一个打开的文件流。成功时返回0,失败时返回EOF,并设置errno以指示错误。
用法示例:
fclose(fp); // 关闭之前打开的文件
fread() 和 fwrite()
函数原型:
#include <stdio.h> size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream); size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream); 功能:
fread()
用于从指定的文件流中读取数据,fwrite()
用于向文件流中写入数据。ptr
是指向数据缓冲区的指针,size
是每个数据项的大小(以字节为单位),nmemb
是要读取或写入的数据项的数量,stream
是文件流的指针。用法示例:
char buffer[100]; size_t numRead = fread(buffer, sizeof(char), 100, fp); // 从文件读取数据 size_t numWritten = fwrite("Hello, world!", sizeof(char), 13, fp); // 向文件写入数据 fseek()
函数原型:
#include <stdio.h> int fseek(FILE *stream, long offset, int whence); 功能:将文件指针移动到指定的位置。
offset
是相对于whence
参数的偏移量,whence
可以是SEEK_SET
(文件开头)、SEEK_CUR
(当前位置)、或SEEK_END
(文件末尾)。用法示例:
fseek(fp, 0, SEEK_SET); // 将文件指针移动到文件开头
ftell()
函数原型:
#include <stdio.h> long ftell(FILE *stream); 功能:返回当前文件指针的位置。如果出错,则返回-1L。
用法示例:
long currentPosition = ftell(fp); // 获取当前文件指针的位置
remove()
函数原型:
#include <stdio.h> int remove(const char *filename); 功能:删除指定的文件。成功时返回0,失败时返回非0值。
用法示例:
if (remove("example.txt") != 0) { perror("Error deleting file"); return(-1); } rename()
函数原型(注意:在C标准库中并不直接提供
rename
函数,但它通常在POSIX系统中可用,并包含在stdio.h
或unistd.h
中):#include <stdio.h> // 某些环境可能需要#include <unistd.h> int rename(const char *oldname, const char *newname); 功能:重命名文件或目录。成功时返回0,失败时返回-1,并设置errno以指示错误。
用法示例:
if (rename("oldfile.txt", "newfile.txt") != 0) { perror("Error renaming file"); return(-1); } 请注意,
rename()
函数的行为(特别是在跨不同文件系统或不同目录间重命名文件时)可能因操作系统而异。在大多数Unix-like系统(包括Linux和macOS)中,rename()
函数能够原子性地重命名或移动文件(如果新名称位于不同的目录中)。这意味着在重命名过程中,文件不会因为系统的其他部分同时访问它而处于不一致的状态。然而,在Windows系统中,
rename()
(如果通过C标准库或类似的接口访问)的行为可能略有不同,特别是在尝试将文件移动到不同的卷(驱动器)上时。在Windows中,这通常需要一个先复制后删除的过程,这可能会引入额外的复杂性,比如文件访问权限问题或需要处理文件名冲突等。跨平台注意事项
-
文件路径:在不同操作系统中,文件路径的表示方式可能不同。Unix-like系统使用正斜杠(
/
)作为目录分隔符,而Windows则通常使用反斜杠(\
),尽管Windows也支持正斜杠。 -
换行符:文本文件的换行符也可能不同。Unix-like系统使用
\n
作为换行符,而Windows则使用\r\n
。在跨平台编程时,需要注意这一点,尤其是在读写文本文件时。 -
文件访问权限:在某些操作系统中,文件的访问权限可能受到更严格的控制。在尝试打开、删除或重命名文件之前,请确保您的程序具有适当的权限。
8. 进程控制(通常涉及POSIX或特定操作系统的API)
C标准库本身不直接提供跨平台的进程控制功能,但POSIX标准定义了一系列用于进程控制的函数,如:
-
fork()
:创建子进程。 -
exec()
系列函数(如execl()
,execp()
,execvp()
等):在子进程中执行一个新程序。 -
wait()
和waitpid()
:等待子进程结束,并回收资源。 -
getpid()
和getppid()
:分别获取当前进程的ID和父进程的ID。 -
signal()
和更现代的sigaction()
:用于设置信号处理程序,以捕获和响应进程接收到的信号(如SIGINT表示中断信号)。 -
kill()
和raise()
:kill()
函数用于向另一个进程发送信号,而raise()
函数用于向当前进程发送信号。 -
pause()
:使当前进程暂停执行,直到接收到一个信号。
注意:进程控制函数(如 fork()
, exec()
, wait()
, kill()
等)主要基于 UNIX 和类 UNIX 系统(如 Linux)的 POSIX 标准。在 Windows 系统中,这些函数不直接可用,而需要使用 Windows 特定的 API,如 CreateProcess()
, TerminateProcess()
, WaitForSingleObject()
等,来进行进程控制。
进程控制函数的详解及用法
1. fork()
函数原型:
#include <unistd.h> | |
pid_t fork(void); |
功能:创建一个新的进程(称为子进程),它是调用进程(称为父进程)的一个副本。
返回值:
- 在父进程中,
fork()
返回新创建的子进程的PID。 - 在子进程中,
fork()
返回0。 - 如果出现错误,则返回-1。
用法示例:
pid_t pid = fork(); | |
if (pid == -1) { | |
// 错误处理 | |
} else if (pid == 0) { | |
// 子进程代码 | |
} else { | |
// 父进程代码 | |
} |
2. exec() 系列函数
函数原型(示例为execl()
):
#include <unistd.h> | |
int execl(const char *path, const char *arg, ..., /* (char *) NULL */); |
功能:在子进程中加载并执行指定的程序,替换子进程的映像和数据,但进程ID不变。
返回值:execl()
等exec
函数在成功时不返回;在出错时返回-1,但实际上由于它们替换了进程映像,出错的情况很少见。
用法示例(使用execl()
):
if (fork() == 0) { | |
execl("/bin/ls", "ls", "-l", (char *)NULL); | |
// 如果execl执行成功,则不会执行到这里;如果失败,则会执行到这里 | |
perror("execl failed"); | |
exit(EXIT_FAILURE); | |
} |
3. wait() 和 waitpid()
函数原型:
#include <sys/types.h> | |
#include <sys/wait.h> | |
pid_t wait(int *status); | |
pid_t waitpid(pid_t pid, int *status, int options); |
功能:等待子进程结束,并回收子进程的资源。wait()
等待任一子进程,而waitpid()
可以等待特定的子进程或一组子进程。
返回值:
- 成功时返回结束的子进程的PID。
- 如果设置了
WNOHANG
且没有子进程结束,则返回0。 - 如果出错,则返回-1。
用法示例(使用wait()
):
int status; | |
pid_t pid = wait(&status); | |
if (pid == -1) { | |
// 错误处理 | |
} | |
// 处理子进程结束 |
4. getpid() 和 getppid()
函数原型:
#include <unistd.h> | |
pid_t getpid(void); | |
pid_t getppid(void); |
功能:getpid()
返回当前进程的PID,getppid()
返回当前进程的父进程的PID。
用法示例:
pid_t pid = getpid(); | |
pid_t ppid = getppid(); | |
printf("PID: %d, PPID: %d\n", pid, ppid); |
5. signal() 和 sigaction()
函数原型(signal()
):
#include <signal.h> | |
void (*signal(int sig, void (*func)(int)))(int); |
函数原型(sigaction()
):
#include <signal.h> | |
int sigaction(int sig, const struct sigaction *act, struct sigaction *oact); |
功能:signal()
用于设置信号处理函数,但其行为在不同系统上可能不一致。sigaction()
提供了一个更可靠的方式来设置信号处理程序,并支持更复杂的信号处理选项。
用法示例(sigaction()
):
#include <stdio.h> | |
#include <stdlib.h> | |
#include <signal.h> | |
#include <string.h> | |
#include <unistd.h> | |
void signal_handler(int signum) { | |
printf("Caught signal %d\n", signum); | |
// 清理并退出 | |
exit(signum); | |
} | |
int main() { | |
struct sigaction sa; | |
sa.sa_handler = signal_handler; | |
sigemptyset(&sa.sa_mask); | |
sa.sa_flags = 0; | |
sigaction(SIGINT, &sa, NULL); | |
while (1) { | |
pause(); // 等待信号 | |
} | |
return 0; | |
} |
6. kill() 和 raise()
函数原型:
#include <signal.h> | |
int kill(pid_t pid, int sig); | |
int raise(int sig); |
功能:kill()
用于向另一个进程发送信号,raise()
用于向当前进程发送信号。
返回值:成功时返回0,出错时返回-1。
用法示例(kill()
):
pid_t pid = getpid(); // 仅为示例,实际中应使用要发送信号的进程ID | |
if (kill(pid, SIGINT) == -1) { | |
perror("kill failed"); | |
} |
用法示例(raise()
):
if (raise(SIGINT) == -1) { | |
perror("raise failed"); | |
} |
7. pause()
函数原型:
#include <unistd.h> | |
int pause(void); |
功能:使调用进程挂起(暂停执行),直到接收到一个信号。
返回值:pause()
函数总是返回-1,并设置errno
为EINTR
,表示它被信号中断。
用法示例(已在sigaction()
示例中展示):
pause(); // 等待信号
这些函数提供了进程控制的核心功能,是Unix和类Unix系统编程中不可或缺的一部分。在实际应用中,应根据具体需求合理选择和使用这些函数。
总结
C语言的标准库为开发者提供了丰富的函数集,覆盖了从基本输入输出、字符串与字符处理、数学运算到高级的内存管理、时间日期处理、文件操作及进程控制等多个方面。掌握这些常用库函数的使用,对于高效地编写C语言程序至关重要。然而,对于进程控制等特定于操作系统的功能,开发者需要了解并适应不同操作系统平台上的API差异。