C 语言字符串和格式化输入输出
C 语言字符串
字符串的定义与表示
在 C 语言中,字符串是一个以空字符 '\0'
结尾的字符数组。例如,我们可以这样定义一个字符串:
char str1[] = "Hello, World!";
这里,str1
是一个字符数组,数组的大小会根据字符串常量的长度自动确定,并且编译器会自动在字符串末尾添加 '\0'
。所以,str1
实际上占用 14 个字节(12 个字符加上 '\0'
和可能的填充字节,具体取决于系统)。
我们也可以先定义字符数组,然后再赋值:
char str2[20];
strcpy(str2, "Hello");
这里,str2
被定义为一个长度为 20 的字符数组,strcpy
函数用于将字符串 "Hello" 复制到 str2
中。注意,str2
的长度必须足够容纳要复制的字符串及其结束符 '\0'
,否则会导致缓冲区溢出,这是一种严重的安全漏洞。
字符串的存储与内存布局
当我们定义一个字符串时,它在内存中的存储方式如下:
char str[] = "test";
在内存中,str
数组的布局为:
内存地址 | 存储内容 |
---|---|
&str[0] | 't' |
&str[1] | 'e' |
&str[2] | 's' |
&str[3] | 't' |
&str[4] | '\0' |
字符串常量通常存储在只读数据段中,例如:
const char *ptr = "constant string";
这里,"constant string"
存储在只读数据段,ptr
是一个指向该字符串的指针。试图通过 ptr
修改字符串内容会导致未定义行为。
字符串操作函数
-
字符串复制函数
strcpy
#include <stdio.h> #include <string.h> int main() { char source[] = "Hello"; char destination[20]; strcpy(destination, source); printf("Copied string: %s\n", destination); return 0; }
strcpy
函数将源字符串source
复制到目标字符串destination
中,包括结束符'\0'
。它会覆盖目标字符串中已有的内容,并且不会检查目标缓冲区是否足够大,因此使用时要小心。 -
字符串连接函数
strcat
#include <stdio.h> #include <string.h> int main() { char str1[20] = "Hello"; char str2[] = ", World!"; strcat(str1, str2); printf("Concatenated string: %s\n", str1); return 0; }
strcat
函数将str2
连接到str1
的末尾,并且str1
必须有足够的空间来容纳连接后的字符串。连接时,str1
末尾的'\0'
会被str2
的第一个字符覆盖,然后str2
的结束符'\0'
会被添加到连接后的字符串末尾。 -
字符串比较函数
strcmp
#include <stdio.h> #include <string.h> int main() { char str1[] = "apple"; 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; }
strcmp
函数按字典序比较两个字符串。如果str1
小于str2
,返回一个负整数;如果str1
大于str2
,返回一个正整数;如果两个字符串相等,返回 0。比较是基于字符的 ASCII 值进行的。 -
字符串长度计算函数
strlen
#include <stdio.h> #include <string.h> int main() { char str[] = "Hello, World!"; size_t length = strlen(str); printf("Length of the string: %zu\n", length); return 0; }
strlen
函数返回字符串的长度,不包括结束符'\0'
。它从字符串的起始位置开始计数,直到遇到'\0'
为止。
字符串的遍历与操作
遍历字符串是对字符串进行各种操作的基础。我们可以使用循环来遍历字符串中的每个字符。例如,统计字符串中字母 a
的个数:
#include <stdio.h>
int main() {
char str[] = "banana";
int count = 0;
for (int i = 0; str[i] != '\0'; i++) {
if (str[i] == 'a') {
count++;
}
}
printf("Number of 'a' in the string: %d\n", count);
return 0;
}
在这个例子中,我们使用 for
循环遍历字符串 str
,通过判断当前字符是否为 'a'
来进行计数。
字符串与指针
在 C 语言中,字符串与指针密切相关。我们可以使用字符指针来操作字符串。例如:
#include <stdio.h>
int main() {
const char *ptr = "Hello";
printf("String: %s\n", ptr);
return 0;
}
这里,ptr
是一个指向字符串常量 "Hello" 的指针。我们可以通过指针来访问字符串中的字符,例如:
#include <stdio.h>
int main() {
const char *ptr = "Hello";
printf("First character: %c\n", *ptr);
return 0;
}
*ptr
表示指针 ptr
所指向的字符,即字符串的第一个字符 'H'
。
我们还可以通过指针运算来遍历字符串:
#include <stdio.h>
int main() {
const char *ptr = "Hello";
while (*ptr != '\0') {
printf("%c ", *ptr);
ptr++;
}
printf("\n");
return 0;
}
在这个例子中,通过不断递增指针 ptr
,我们遍历了整个字符串并逐个输出字符。
动态分配内存的字符串
有时候,我们无法在编译时确定字符串的长度,这时就需要动态分配内存。例如,从用户输入获取一个字符串并存储:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
int main() {
char *str;
int size = 100;
str = (char *)malloc(size * sizeof(char));
if (str == NULL) {
printf("Memory allocation failed\n");
return 1;
}
printf("Enter a string: ");
fgets(str, size, stdin);
// fgets 会读取换行符,我们可以将其替换为 '\0'
str[strcspn(str, "\n")] = '\0';
printf("You entered: %s\n", str);
free(str);
return 0;
}
在这个例子中,我们使用 malloc
函数动态分配了 size
个字节的内存来存储字符串。fgets
函数用于从标准输入读取字符串,它会读取最多 size - 1
个字符,以防止缓冲区溢出。最后,我们使用 free
函数释放分配的内存,避免内存泄漏。
C 语言格式化输入输出
格式化输出函数 printf
printf
是 C 语言中最常用的格式化输出函数,它用于将格式化的数据输出到标准输出(通常是控制台)。其基本语法为:
int printf(const char *format, ...);
format
是一个字符串,包含普通字符和格式说明符。格式说明符以 %
开头,用于指定要输出的数据类型和格式。例如:
#include <stdio.h>
int main() {
int num = 10;
float fnum = 3.14;
char ch = 'A';
printf("Integer: %d\n", num);
printf("Float: %f\n", fnum);
printf("Character: %c\n", ch);
return 0;
}
在这个例子中,%d
用于输出整数,%f
用于输出浮点数,%c
用于输出字符。
常用格式说明符
- 整数格式说明符
%d
:以十进制形式输出有符号整数。%u
:以十进制形式输出无符号整数。%o
:以八进制形式输出无符号整数。%x
或%X
:以十六进制形式输出无符号整数,%x
使用小写字母,%X
使用大写字母。
#include <stdio.h> int main() { int num = 255; printf("Decimal: %d\n", num); printf("Unsigned decimal: %u\n", num); printf("Octal: %o\n", num); printf("Hexadecimal (lowercase): %x\n", num); printf("Hexadecimal (uppercase): %X\n", num); return 0; }
- 浮点数格式说明符
%f
:以十进制小数形式输出浮点数,默认保留 6 位小数。%e
或%E
:以科学计数法形式输出浮点数,%e
使用小写e
,%E
使用大写E
。%g
或%G
:根据数值的大小自动选择%f
或%e
格式,以较短的表示形式输出。
#include <stdio.h> int main() { float fnum = 12345.6789; printf("Decimal float: %f\n", fnum); printf("Scientific notation (lowercase): %e\n", fnum); printf("Scientific notation (uppercase): %E\n", fnum); printf("Auto - selected format: %g\n", fnum); return 0; }
- 字符和字符串格式说明符
%c
:输出单个字符。%s
:输出字符串。
#include <stdio.h> int main() { char ch = 'B'; char str[] = "Hello"; printf("Character: %c\n", ch); printf("String: %s\n", str); return 0; }
格式修饰符
除了基本的格式说明符,printf
还支持一些格式修饰符,用于更精细地控制输出格式。
- 宽度修饰符:可以指定输出字段的最小宽度。例如,
%5d
表示输出的整数至少占用 5 个字符的宽度,如果整数本身不足 5 位,会在前面填充空格。#include <stdio.h> int main() { int num = 12; printf("Width 5: |%5d|\n", num); return 0; }
- 精度修饰符:对于浮点数,用于指定小数点后的位数;对于字符串,用于指定输出的最大字符数。例如,
%.2f
表示输出浮点数时保留 2 位小数,%.5s
表示输出字符串时最多输出 5 个字符。#include <stdio.h> int main() { float fnum = 3.14159; char str[] = "Hello, World!"; printf("Float with 2 decimal places: %.2f\n", fnum); printf("First 5 characters of string: %.5s\n", str); return 0; }
- 左对齐修饰符
-
:默认情况下,输出是右对齐的。使用-
修饰符可以使输出左对齐。例如,%-5d
表示输出的整数左对齐,占用 5 个字符的宽度。#include <stdio.h> int main() { int num = 12; printf("Left - aligned width 5: |%-5d|\n", num); return 0; }
格式化输入函数 scanf
scanf
用于从标准输入读取格式化的数据。其基本语法为:
int scanf(const char *format, ...);
format
字符串包含格式说明符,用于指定要读取的数据类型。与 printf
类似,scanf
的格式说明符以 %
开头。例如:
#include <stdio.h>
int main() {
int num;
float fnum;
char ch;
printf("Enter an integer, a float, and a character: ");
scanf("%d %f %c", &num, &fnum, &ch);
printf("You entered: %d, %f, %c\n", num, fnum, ch);
return 0;
}
在这个例子中,scanf
从标准输入读取一个整数、一个浮点数和一个字符,并分别存储到变量 num
、fnum
和 ch
中。注意,在 scanf
中,变量名前需要加上 &
运算符,以获取变量的地址,这样 scanf
才能将读取的数据存储到正确的位置。
常见格式说明符与 scanf
的使用
- 整数输入:
%d
用于读取有符号整数,%u
用于读取无符号整数。#include <stdio.h> int main() { int signed_num; unsigned int unsigned_num; printf("Enter a signed integer and an unsigned integer: "); scanf("%d %u", &signed_num, &unsigned_num); printf("Signed integer: %d, Unsigned integer: %u\n", signed_num, unsigned_num); return 0; }
- 浮点数输入:
%f
用于读取单精度浮点数,%lf
用于读取双精度浮点数。#include <stdio.h> int main() { float fnum; double dnum; printf("Enter a float and a double: "); scanf("%f %lf", &fnum, &dnum); printf("Float: %f, Double: %lf\n", fnum, dnum); return 0; }
- 字符和字符串输入:
%c
用于读取单个字符,%s
用于读取字符串。读取字符串时,scanf
会在遇到空白字符(空格、制表符、换行符等)时停止读取。#include <stdio.h> int main() { char ch; char str[20]; printf("Enter a character and a string: "); scanf(" %c %s", &ch, str); // 在 %c 前加一个空格,以跳过前面的空白字符 printf("Character: %c, String: %s\n", ch, str); return 0; }
scanf
的注意事项
- 空白字符处理:
scanf
在读取输入时,会自动跳过开头的空白字符(空格、制表符、换行符等),除了%c
格式说明符。如果要读取包含空白字符的字符串,可以使用fgets
函数代替scanf
。 - 缓冲区溢出:当使用
%s
读取字符串时,scanf
不会检查目标数组是否足够大,容易导致缓冲区溢出。例如:
为了避免缓冲区溢出,可以使用#include <stdio.h> int main() { char str[5]; printf("Enter a string: "); scanf("%s", str); // 如果输入超过 4 个字符(不包括 '\0'),会导致缓冲区溢出 printf("You entered: %s\n", str); return 0; }
fgets
并对输入进行处理,或者使用scanf
的宽度限定修饰符,如%4s
表示最多读取 4 个字符到str
中。
其他输入输出函数
- 字符输入输出函数
getchar
和putchar
getchar
用于从标准输入读取一个字符,它返回读取的字符的 ASCII 值。例如:
#include <stdio.h> int main() { char ch; printf("Enter a character: "); ch = getchar(); printf("You entered: %c\n", ch); return 0; }
putchar
用于将一个字符输出到标准输出。例如:
#include <stdio.h> int main() { char ch = 'A'; putchar(ch); putchar('\n'); return 0; }
- 字符串输入输出函数
gets
和puts
gets
用于从标准输入读取一行字符串,直到遇到换行符为止,并将换行符替换为'\0'
。但是,gets
存在缓冲区溢出风险,已被 C11 标准弃用。puts
用于将字符串输出到标准输出,并在末尾自动添加换行符。例如:
#include <stdio.h> int main() { char str[20]; printf("Enter a string: "); // gets(str); // 不推荐使用 fgets(str, 20, stdin); str[strcspn(str, "\n")] = '\0'; puts(str); return 0; }
- 文件输入输出函数
fprintf
和fscanf
fprintf
用于将格式化的数据输出到文件。其语法为:
例如,将数据写入文件:int fprintf(FILE *stream, const char *format, ...);
#include <stdio.h> int main() { FILE *file = fopen("output.txt", "w"); if (file == NULL) { printf("Failed to open file\n"); return 1; } int num = 10; float fnum = 3.14; fprintf(file, "Integer: %d, Float: %f\n", num, fnum); fclose(file); return 0; }
fscanf
用于从文件读取格式化的数据。其语法为:
例如,从文件读取数据:int fscanf(FILE *stream, const char *format, ...);
#include <stdio.h> int main() { FILE *file = fopen("input.txt", "r"); if (file == NULL) { printf("Failed to open file\n"); return 1; } int num; float fnum; fscanf(file, "Integer: %d, Float: %f", &num, &fnum); printf("Read from file: %d, %f\n", num, fnum); fclose(file); return 0; }
通过对 C 语言字符串和格式化输入输出的深入了解,开发者能够更好地处理文本数据的输入输出、存储和操作,这对于编写各种类型的 C 语言程序,无论是简单的控制台应用还是复杂的系统软件,都是至关重要的基础技能。在实际编程中,要注意字符串操作的安全性,避免缓冲区溢出等问题,同时合理使用格式化输入输出函数,以满足不同的需求。