11.1 表示字符串和字符串I/O
11.1.1 在程序中定义字符串
1.字符串字面量
用双引号括起来的内容称为字符串字面量(string literal),也叫作字符串常量(string constant)。双引号中的字符和编译器自动加入末尾的\0字符,都作为字符串存储在内存中
从ANSI C标准起,如果字符串字面量之间没有间隔,或者用空白字符分隔,C会将其视为串联起来的字符串字面量。例如:
char greeting[50] = “Hello, and”" how are" " you"
" today!";
与下面的代码等价:
char greeting[50] = “Hello, and how are you today!”;
字符串常量属于静态存储类别(static storage class),这说明如果在函数中使用字符串常量,该字符串只会被存储一次,在整个程序的生命期内存在,即使函数被调用多次。用双引号括起来的内容被视为指向该字符串存储位置的指针。这类似于把数组名作为指向该数组位置的指针。
2.字符串数组和初始化
定义字符串数组:
const char m1[40] = "Limit youtself to one line's worth.";
可以省略数组大小,让编译器自动计算数组大小:
const char m2[] = "Limit youtself to one line's worth.";
使用指针表示法创建字符串:
const char* pt1 = "Something is pointing at me.";
3.数组和指针
-
对于数组来说
在内存中分配为一个内含 29 个元素的数组(28 个对应字符及一个末尾空字符'\0'
),元素被初始化为字符串字面量对应的字符,字符串存储在静态存储区,程序运行时将字符串拷贝到数组中,此时有两个字符串副本,一个在静态内存中的字符串字面量,另一个在数组中。
数组名ar1
是该数组首元素地址(&ar1[0]
)的别名,是地址常量,不能更改其值(即不能改变数组的存储位置),可以进行ar1 + 1
操作指向数组下一个元素,但不能进行++ar1
这样的操作,因为递增运算符不能用于常量
-
对于指针来说
为字符串在静态存储区预留 29 个元素的空间,程序运行时为指针变量pt1
留出存储位置,并把字符串的地址存储在指针变量中,指针最初指向字符串首字符,其值可以改变,可使用递增运算符(如++pt1
将指向第 2 个字符)。
由于字符串字面量被视为const
数据,应把指针pt1
声明为指向const
数据的指针,即不能用pt1
改变它所指向的数据,但可以改变pt1
的值。(改指针变量,保留常数数据)
另外,初始化数组把静态存储区的字符串拷贝到数组中,而初始化指针只把字符串的地址拷贝给指针。
示例:
// addresses.c -- 字符串的地址
#define MSG "I'm special"
#include <stdio.h>
int main()
{
char ar[] = MSG;
const char* pt = MSG;
printf("address of \"I'm special\": %p \n", "I'm special");
printf(" address ar: %p\n", ar);
printf(" address pt: %p\n", pt);
printf(" address of MSG: %p\n", MSG);
printf("address of \"I'm special\": %p \n", "I'm special");
return 0;
}
示例结果:
address of "I'm special": 00007FF73871AC10
address ar: 000000647B10F508
address pt: 00007FF73871AC10
address of MSG: 00007FF73871AC10
address of "I'm special": 00007FF73871AC10
4.数组和指针的区别
一、指针的递增操作
只有指针表示法可以进行递增操作,如在循环中通过putchar(*(head++));
可以打印字符并使指针指向下一个位置。
二、数组与指针赋值的区别
可以将指针赋值给数组名(即让指针指向数组的首元素),如head = heart;
,但不能将数组名赋值给指针,如heart = head;
,因为数组名是地址常量,不能作为赋值运算符的左侧。
三、指针初始化与修改字符串字面量
- 当使用
char * word = “frame”;
这样未使用const
限定符的指针初始化时,尝试用word[1] = ‘l’;
修改字符串可能导致未定义行为,因为编译器可能对完全相同的字符串字面量使用内存中的一个副本来表示。 - 例如对
char * p1 = “Klingon”;
,如果修改p1[0]
,可能会影响所有使用该字符串字面量的地方,导致不可预期的结果。 - 为避免这种不可预期的行为,建议在把指针初始化为字符串字面量时使用
const
限定符,如果打算修改字符串,就不要用指针指向字符串字面量。
11.1.2 指针和字符串
示例结果:
Don't be a fool!
mesg = Don't be a fool!; &mesg = 000000000062FE18; value = 0000000000404000
copy = Don't be a fool!; © = 000000000062FE10; value = 0000000000404000
- 我们来仔细分析最后两个printf()的输出。首先第1项,mesg和copy都以字符串形式输出(%s转换说明)。这里没问题,两个字符串都是"Don’t be a fool!"。
- 接着第2项,打印两个指针的地址。如上输出所示,指针mesg和copy分别存储在地址为0x0012ff48和0x0012ff44的内存中。
- 注意最后一项,显示两个指针的值。所谓指针的值就是它存储的地址。mesg和copy的值都是0x0040a000,说明它们都指向的同一个位置。因此,程序并未拷贝字符串。语句copy = mesg;把mesg的值赋给copy,即让copy也指向mesg指向的字符串。
11.2 字符串输入
11.2.1 分配空间
*char name;
scanf("%s", name);
虽然可能会通过编译(编译器很可能给出警告),但是在读入name时,name可能会擦写掉程序中的数据或代码,从而导致程序异常中止。因为scanf()要把信息拷贝至参数指定的地址上,而此时该参数是个未初始化的指针,name可能会指向任何地方。
最简单的方法是,在声明时显式指明数组的大小:
char name[81];
11.2.2 不幸的gets()函数
在读取字符串时,scanf()和转换说明%s只能读取一个单词。可是在程序中经常要读取一整行输入,而不仅仅是一个单词。许多年前,gets()函数就用于处理这种情况。gets()函数简单易用,它读取整行输入,直至遇到换行符,然后丢弃换行符,存储其余字符,并在这些字符的末尾添加一个空字符使其成为一个C字符串。它经常和puts()函数配对使用,该函数用于显示字符串,并在末尾添加换行符。
/* getsputs.c -- 使用 gets() 和 puts() */
#include <stdio.h>
#define STLEN 81
int main(void)
{
char words[STLEN];
puts("Enter a string, please.");
gets(words); // 典型用法
printf("Your string twice:\n");
printf("%s\n", words);
puts(words);
puts("Done.");
return 0;
}
下面是该程序在某些编译器(或者至少是旧式编译器)中的运行示例:
Enter a string, please.I want to learn about string theory!
Your string twice:
I want to learn about string theory!
I want to learn about string theory!
Done.
下面是该程序在另一个编译器中的输出示例:
Enter a string, please.
warning: this program uses gets(), which is unsafe.Oh, no!
Your string twice:
Oh, no!
Oh, no!
Done.
gets()
函数只有一个参数即数组名(会被转换为数组首元素地址),它无法检查目标数组是否能容纳输入的字符串,存在缓冲区溢出的风险。- 如果输入的字符串过长,可能会擦写程序中的其他数据导致程序异常中止或出现其他不可预期的情况。
- 由于其不安全行为造成安全隐患,有人曾利用它插入破坏系统安全的代码。
11.2.3 gets()函数
//由于可能导致缓冲区溢出,产生安全问题。在C11标准中已经被废除
gets() 读取整行输入,直到遇到换行符,然后丢弃换行符。
puts() 用于显示字符串,并在末尾添加换行符。
11.2.4 gets()的替代品
fgets()
函数
fgets()
函数有三个参数,第二个参数指明读入字符的最大数量,如果该参数值是n
,则会读入n - 1
个字符或者读到遇到的第一个换行符为止。- 如果
fgets()
读到换行符,会把它存储在字符串中,这与gets()
函数不同,gets()
会丢弃换行符。 - 第三个参数指明要读入的文件,若读入从键盘输入的数据,以
stdin
(标准输入)作为参数,定义在<stdio.h>
头文件中。
通常fgets()
函数与fputs()
函数配对使用,因为fgets()
会在字符串末尾保留换行符(假设输入行不溢出),而fputs()
函数的第二个参数指明要写入的文件,若要显示在计算机显示器上,使用stdout
(标准输出)作为参数。
gets_s() 函数
- 安全性:
gets_s()
可以防止缓冲区溢出。它接受两个参数,第一个参数是存储输入的字符数组,第二个参数是数组的大小,确保不会读取超过数组容量的字符。
- 错误处理:
- 如果输入的字符串长度超过了数组大小减一(要留一个位置给字符串结束符
'\0'
),gets_s()
会立即停止读取并返回一个错误码。 - 如果没有错误发生,它会返回非零值;如果发生错误,会返回 0。
- 如果输入的字符串长度超过了数组大小减一(要留一个位置给字符串结束符
#include <stdio.h>
int main() {
char str[10];
gets_s(str, sizeof(str));
printf("%s\n", str);
return 0;
}
在使用gets_s()
时,务必确保第二个参数正确反映了数组的大小,以保证程序的安全性。
11.2.4 scanf()函数
scanf()
更像是 “获取单词” 函数而非 “获取字符串” 函数。- 确定输入结束的方式有两种:一是使用
%s
转换说明时,以下一个空白字符(空行、空格、制表符或换行符)作为字符串的结束,字符串不包括这些空白字符;二是如果指定了字段宽度(如%10s
),那么scanf()
将读取指定数量的字符或者读到第一个空白字符停止,哪个条件先满足就以哪个作为结束输入的条件。 scanf()
从第一个非空白字符作为字符串的开始。
示例:
/* scan_str.c -- 使用 scanf() */
#include <stdio.h>
int main(void)
{
char name1[11], name2[11];
int count;
printf("Please enter 2 names.\n");
count = scanf("%5s %10s", name1, name2);
printf("I read the %d names %s and %s.\n", count, name1, name2);
return 0;
}
示例结果:
Please enter 2 names.Jesse Jukes
I read the 2 names Jesse and Jukes.
Please enter 2 names.Liza Applebottham
I read the 2 names Liza and Applebotth.
Please enter 2 names.Portensia Callowit
I read the 2 names Porte and nsia.
11.3 字符串输出
/* put_out.c -- 使用 puts() */
#include <stdio.h>
#define DEF "I am a #defined string."
int main(void)
{
char str1[80] = "An array was initialized to me.";
const char * str2 = "A pointer was initialized to me.";
puts("I'm an argument to puts().");
puts(DEF);
puts(str1);
puts(str2);
puts(&str1[5]);
puts(str2 + 4);
return 0;
}
11.4 自定义输入/输出函数
可以利用getchar()和putchar()自定义函数来处理字符串。
11.5 字符串函数 string.h
-
strlen()函数
strlen()函数用于统计字符串的长度。下面的函数可以缩短字符串的长度,其中用到了strlen():
void fit(char *string, unsigned int size)
{
if (strlen(string) > size)
string[size] = '\0';
}
下面是该程序的输出:
Things should be as simple as possible, but not simpler.
Things should be as simple as possible
Let’s look at some more of the string.
but not simpler.
fit()函数把第39个元素的逗号替换成’\0’字符。puts()函数在空字符处停止输出,并忽略其余字符。然而,这些字符还在缓冲区中,下面的函数调用把这些字符打印了出来:
puts(mesg + 39);
表达式mesg + 39是mesg[39]的地址,该地址上存储的是空格字符。所以puts()显示该字符并继续输出直至遇到原来字符串中的空字符。(能够输出后续的底层逻辑)
-
strcat()函数
strcat()(用于拼接字符串)函数接受两个字符串作为参数。该函数把第2个字符串的备份附加在第1个字符串末尾,并把拼接后形成的新字符串作为第1个字符串,第2个字符串不变。strcat()函数的类型是char *(即,指向char的指针)。strcat()函数返回第1个参数,即拼接第2个字符串后的第1个字符串的地址。
示例:
/* str_cat.c -- 拼接两个字符串 */
#include <stdio.h>
#include <string.h> /* strcat()函数的原型在该头文件中 */
#define SIZE 80
char * s_gets(char * st, int n);
int main(void)
{
char flower[SIZE];
char addon [] = "s smell like old shoes.";
puts("What is your favorite flower?");
if (s_gets(flower, SIZE))
{
strcat(flower, addon);
puts(flower);
puts(addon);
}
else
puts("End of file encountered!");
puts("bye");
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
示例结果:
What is your favorite flower?wonderflower
wonderflowers smell like old shoes.
s smell like old shoes.
bye
-
strncat()函数
与strcat()函数的差别
strcat()函数无法检查第1个数组是否能容纳第2个字符串。如果分配给第1个数组的空间不够大,多出来的字符溢出到相邻存储单元时就会出问题。
用strncat(),该函数的第3个参数指定了最大添加字符数。
/* join_chk.c -- 拼接两个字符串,检查第1个数组的大小 */
#include <stdio.h>
#include <string.h>
#define SIZE 30
#define BUGSIZE 13
char * s_gets(char * st, int n);
int main(void)
{
char flower[SIZE];
char addon [] = "s smell like old shoes.";
char bug[BUGSIZE];
int available;
puts("What is your favorite flower?");
s_gets(flower, SIZE);
if ((strlen(addon) + strlen(flower) + 1) <= SIZE)
strcat(flower, addon);
puts(flower);
puts("What is your favorite bug?");
s_gets(bug, BUGSIZE);
available = BUGSIZE - strlen(bug) - 1;
strncat(bug, addon, available);
puts(bug);
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
-
strcmp()函数
使用C标准库中的strcmp()函数(用于字符串比较)。该函数通过比较运算符来比较字符串,就像比较数字一样。如果两个字符串参数相同,该函数就返回0,否则返回非零值。
示例:
/* compare.c -- 该程序可以正常运行 */
#include <stdio.h>
#include <string.h> // strcmp()函数的原型在该头文件中
#define ANSWER "Grant"
#define SIZE 40
char * s_gets(char * st, int n);
int main(void)
{
char try[SIZE];
puts("Who is buried in Grant's tomb?");
s_gets(try, SIZE);
while (strcmp(try, ANSWER) != 0)
{
puts("No, that's wrong. Try again.");
s_gets(try, SIZE);
}
puts("That's right!");
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}/* compback.c -- strcmp()的返回值 */
#include <stdio.h>
#include <string.h>
int main(void)
{
printf("strcmp(\"A\", \"A\") is ");
printf("%d\n", strcmp("A", "A"));
printf("strcmp(\"A\", \"B\") is ");
printf("%d\n", strcmp("A", "B"));
printf("strcmp(\"B\", \"A\") is ");
printf("%d\n", strcmp("B", "A"));
printf("strcmp(\"C\", \"A\") is ");
printf("%d\n", strcmp("C", "A"));
printf("strcmp(\"Z\", \"a\") is ");
printf("%d\n", strcmp("Z", "a"));
printf("strcmp(\"apples\", \"apple\") is ");
printf("%d\n", strcmp("apples", "apple"));
return 0;
}
示例结果:
strcmp(“A”, “A”) is 0
strcmp(“A”, “B”) is -1
strcmp(“B”, “A”) is 1
strcmp(“C”, “A”) is 1
strcmp(“Z”, “a”) is -1
strcmp(“apples”, “apple”) is 1
strcmp()比较"A"和本身,返回0;比较"A"和"B",返回-1;比较"B"和"A",返回1。这说明,如果在字母表中第1个字符串位于第2个字符串前面,strcmp()中就返回负数;反之,strcmp()则返回正数。所以,strcmp()比较"C"和"A",返回1。其他系统可能返回2,即两者的ASCII码之差。ASCII标准规定,在字母表中,如果第1个字符串在第2个字符串前面,strcmp()返回一个负数;如果两个字符串相同,strcmp()返回0;如果第1个字符串在第2个字符串后面,strcmp()返回正数。然而,返回的具体值取决于实现。
如果两个字符串开始的几个字符都相同会怎样?一般而言,strcmp()会依次比较每个字符,直到发现第1对不同的字符为止。然后,返回相应的值。例如,在上面的最后一个例子中,"apples"和"apple"只有最后一对字符不同("apples"的s和"apple"的空字符)。由于空字符在ASCII中排第1。字符s一定在它后面,所以strcmp()返回一个正数。
-
strncmp()函数
strcmp()函数比较字符串中的字符,直到发现不同的字符为止,这一过程可能会持续到字符串的末尾。而strncmp()函数在比较两个字符串时,可以比较到字符不同的地方,也可以只比较第3个参数指定的字符数。例如,要查找以"astro"开头的字符串,可以限定函数只查找这5个字符)(通俗来讲,就是多了一个指定功能)
-
strcpy()和strncpy()函数
前面提到过,如果pts1和pts2都是指向字符串的指针,那么下面语句拷贝的是字符串的地址而不是字符串本身:
pts2 = pts1;
如果希望拷贝整个字符串,要使用strcpy()函数。
参考赋值表达式语句,很容易记住strcpy()参数的顺序,即第1个是目标字符串,第2个是源字符串。
char target[20];
int x;
x = 50; /* 数字赋值*/*
strcpy(target, “Hi ho!”); /* 字符串赋值*/
strcpy()函数还有两个有用的属性。第一,strcpy()的返回类型是char *,该函数返回的是第1个参数的值,即一个字符的地址。第二,第1个参数不必指向数组的开始。这个属性可用于拷贝数组的一部分。
/* copy2.c -- 使用 strcpy() */
#include <stdio.h>
#include <string.h> // 提供strcpy()的函数原型
#define WORDS "beast"
#define SIZE 40
int main(void)
{
const char * orig = WORDS;
char copy[SIZE] = "Be the best that you can be.";
char * ps;
puts(orig);
puts(copy);
ps = strcpy(copy + 7, orig);
puts(copy);
puts(ps);
return 0;
}
示例结果:
beast
Be the best that you can be.
Be the beast
beast
更谨慎的选择:strncpy()
strcpy()和strcat()都有同样的问题,它们都不能检查目标空间是否能容纳源字符串的副本。拷贝字符串用strncpy()更安全,该函数的第3个参数指明可拷贝的最大字符数。
strncpy(target, source, n)把source中的n个字符或空字符之前的字符(先满足哪个条件就拷贝到何处)拷贝至target中。因此,如果source中的字符数小于n,则拷贝整个字符串,包括空字符。但是,strncpy()拷贝字符串的长度不会超过n,如果拷贝到第n个字符时还未拷贝完整个源字符串,就不会拷贝空字符。所以,拷贝的副本中不一定有空字符。鉴于此,该程序把n设置为比目标数组大小少1(TARGSIZE-1),然后把数组最后一个元素设置为空字符:
strncpy(qwords[i], temp, TARGSIZE - 1);
qwords[i][TARGSIZE - 1] = ‘\0’;
-
sprintf()函数
sprintf()函数声明在stdio.h中,而不是在string.h中。该函数和printf()类似,但是它是把数据写入字符串,而不是打印在显示器上。
sprintf()的第1个参数是目标字符串的地址。其余参数和printf()相同,即格式字符串和待写入项的列表。
-
其他字符串函数
char *strchr(const char * s, int c);
如果s字符串中包含c字符,该函数返回指向s字符串首次出现的c字符的指针(末尾的空字符也是字符串的一部分,所以在查找范围内);如果在字符串s中未找到c字符,该函数则返回空指针。
char *strpbrk(const char * s1, const char * s2);
如果s1字符中包含s2字符串中的任意字符,该函数返回指向s1字符串首位置的指针;如果在s1字符串中未找到任何s2字符串中的字符,则返回空字符。
char *strrchr(const char * s, char c);
该函数返回s字符串中c字符的最后一次出现的位置(末尾的空字符也是字符串的一部分,所以在查找范围内)。如果未找到c字符,则返回空指针。
char *strstr(const char * s1, const char * s2);
该函数返回指向s1字符串中s2字符串出现的首位置。如果在s1中没有找到s2,则返回空指针。
11.6 字符串示例
/* sort_str.c -- 读入字符串,并排序字符串 */
#include <stdio.h>
#include <string.h>
#define SIZE 81 /* 限制字符串长度,包括 \0 */
#define LIM 20 /* 可读入的最多行数 */
#define HALT "" /* 空字符串停止输入 */
void stsrt(char *strings [], int num); /* 字符串排序函数 */
char * s_gets(char * st, int n);
int main(void)
{
char input[LIM][SIZE]; /* 存储输入的数组 */
char *ptstr[LIM]; /* 内含指针变量的数组 */
int ct = 0; /* 输入计数 */
int k; /* 输出计数 */
printf("Input up to %d lines, and I will sort them.\n", LIM);
printf("To stop, press the Enter key at a line's start.\n");
while (ct < LIM && s_gets(input[ct], SIZE) != NULL
&& input[ct][0] != '\0')
{
ptstr[ct] = input[ct]; /* 设置指针指向字符串 */
ct++;
}
stsrt(ptstr, ct); /* 字符串排序函数 */
puts("\nHere's the sorted list:\n");
for (k = 0; k < ct; k++)
puts(ptstr[k]); /* 排序后的指针 */
return 0;
}
/* 字符串-指针-排序函数 */
void stsrt(char *strings [], int num)
{
char *temp;
int top, seek;
for (top = 0; top < num - 1; top++)
for (seek = top + 1; seek < num; seek++)
if (strcmp(strings[top], strings[seek]) > 0)
{
temp = strings[top];
strings[top] = strings[seek];
strings[seek] = temp;
}
}
char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
示例结果:
Input up to 20 lines, and I will sort them.
To stop, press the Enter key at a line’s start.O that I was where I would be,Then would I be where I am not;But where I am I must be,And where I would be I can not.
Here’s the sorted list:
And where I would be I can not.
But where I am I must be,
O that I was where I would be,
Then would I be where I am not;
巧妙之处:对指向字符串的指针进行排序,而不是对字符串本身进行排序。
一、初始化
程序中指针数组ptrst
中的每个元素(如ptrst[i]
)初始时被设置为指向另一个字符串数组input
中相应的字符串(即ptrst[i]
指向input[i]
的首字符)。
二、排序方式
在排序过程中,程序只是对指针进行重新排列,并不改变input
数组中字符串的实际位置。例如,如果按字母顺序判断出input[1]
应在input[0]
前面,程序就交换指向它们的指针(让ptrst[0]
指向input[1]
的开始,ptrst[1]
指向input[0]
的开始)。
三、优势
这种方式比使用strcpy()
函数直接交换两个input
字符串的内容更加简单高效,同时还保留了input
数组中的原始顺序,因为只是指针发生了改变,字符串在内存中的位置并未变动。
11.7 ctype.h 字符函数 用于处理字符
示例:
/* mod_str.c -- 修改字符串 */
#include <stdio.h>
#include <string.h>
#include <ctype.h>
#define LIMIT 81
void ToUpper(char *);
int PunctCount(const char *);
int main(void)
{
char line[LIMIT];
char * find;
puts("Please enter a line:");
fgets(line, LIMIT, stdin);
find = strchr(line, '\n'); // 查找换行符
if (find) // 如果地址不是 NULL,
*find = '\0'; // 用空字符替换
ToUpper(line);
puts(line);
printf("That line has %d punctuation characters.\n", PunctCount(line));
return 0;
}
void ToUpper(char * str)
{
while (*str)
{
*str = toupper(*str);
str++;
}
}
int PunctCount(const char * str)
{
int ct = 0;
while (*str)
{
if (ispunct(*str))
ct++;
str++;
}
return ct;
}
11.8 命令行参数
使用命令行运行程序时可以在同一行添加参数
程序通过main()的参数读取这些参数,
把字符串转换为数字
- 如果需要整数,可以使用atoi()函数(用于把字母数字转换成整数),该函数接受一个字符串作为参数,返回相应的整数值。
/* hello.c -- 把命令行参数转换为数字 */
#include <stdio.h>
#include <stdlib.h>
int main(int argc, char *argv [])
{
int i, times;
if (argc < 2 || (times = atoi(argv[1])) < 1)
printf("Usage: %s positive-number\n", argv[0]);
else
for (i = 0; i < times; i++)
puts("Hello, good looking!");
return 0;
}
- 程序运行示例
程序运行示例展示了一个命令行输入为 “hello 3” 时的输出情况,连续输出三次 “Hello, good looking!”。同时提到atoi("42regular")
会返回整数 42,但如果命令行参数不是数字,atoi()
函数返回 0,且这种情况下行为是未定义的,使用strtol()
函数进行错误检测更安全。
- 头文件及函数原型
程序中包含了<stdlib.h>
头文件,因为从 ANSI C 开始,该头文件包含了atoi()
函数的原型,同时还包含了atof()
和atol()
函数的原型。atof()
函数把字符串转换成double
类型的值,atol()
函数把字符串转换成long
类型的值,其工作原理与atoi()
类似,分别返回相应类型的值。
- ANSI C 提供的更智能的函数
ANSI C 提供了strtol()
把字符串转换成long
类型的值,strtoul()
把字符串转换成unsigned long
类型的值,strtod()
把字符串转换成double
类型的值。这些函数能识别和报告字符串中的首字符是否是数字,并且strtol()
和strtoul()
还可以指定数字的进制。
/* strcnvt.c -- 使用 strtol() */
#include <stdio.h>
#include <stdlib.h>
#define LIM 30
char * s_gets(char * st, int n);
int main()
{
char number[LIM];
char * end;
long value;
puts("Enter a number (empty line to quit):");
while (s_gets(number, LIM) && number[0] != '\0')
{
value = strtol(number, &end, 10); /* 十进制 */
printf("base 10 input, base 10 output: %ld, stopped at %s (%d)\n",
value, end, *end);
value = strtol(number, &end, 16); /* 十六进制 */
printf("base 16 input, base 10 output: %ld, stopped at %s (%d)\n",
value, end, *end);
puts("Next number:");
}
puts("Bye!\n");
return 0;
}
char * s_gets(char * st, int n)
{
char * ret_val;
int i = 0;
ret_val = fgets(st, n, stdin);
if (ret_val)
{
while (st[i] != '\n' && st[i] != '\0')
i++;
if (st[i] == '\n')
st[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return ret_val;
}
示例结果:
Enter a number (empty line to quit):
10
base 10 input, base 10 output: 10, stopped at (0)
base 16 input, base 10 output: 16, stopped at (0)
Next number:
10atom
base 10 input, base 10 output: 10, stopped at atom (97)
base 16 input, base 10 output: 266, stopped at tom (116)
Next number:Bye!
代码解释:
/* strcnvt.c -- 使用 strtol() */ #include <stdio.h> #include <stdlib.h> #define LIM 30 char * s_gets(char * st, int n);
- 注释表明这个文件的用途是演示使用
strtol()
函数。- 包含了标准输入输出头文件
<stdio.h>
和标准库头文件<stdlib.h>
,后者提供了strtol()
函数的声明。#define LIM 30
定义了一个常量LIM
为 30,可能用于指定字符串的最大长度。char * s_gets(char * st, int n);
是函数原型声明,表明后面的代码中有一个名为s_gets
的函数,它接受一个字符指针和一个整数作为参数,并返回一个字符指针。int main() { char number[LIM]; char * end; long value;
int main()
定义了主函数,主函数返回一个整数。char number[LIM];
声明了一个字符数组number
,长度为LIM
(30),用于存储用户输入的字符串。char * end;
声明了一个字符指针end
,用于接收strtol()
函数返回的转换结束位置的指针。long value;
声明了一个长整型变量value
,用于存储转换后的数字。puts("Enter a number (empty line to quit):"); while (s_gets(number, LIM) && number[0]!= '\0') { value = strtol(number, &end, 10); /* 十进制 */ printf("base 10 input, base 10 output: %ld, stopped at %s (%d)\n", value, end, *end); value = strtol(number, &end, 16); /* 十六进制 */ printf("base 16 input, base 10 output: %ld, stopped at %s (%d)\n", value, end, *end); puts("Next number:"); }
puts("Enter a number (empty line to quit):");
输出提示信息,要求用户输入一个数字,空行表示退出。while (s_gets(number, LIM) && number[0]!= '\0')
循环条件是调用s_gets
函数读取用户输入并存入number
数组,如果读取成功且输入不为空字符串,则进入循环。value = strtol(number, &end, 10);
使用strtol()
函数将输入的字符串number
按照十进制进行转换,结果存储在value
中,end
被设置为转换结束的位置。printf("base 10 input, base 10 output: %ld, stopped at %s (%d)\n", value, end, *end);
输出十进制转换的结果,包括转换后的数字、转换结束的位置以及该位置的字符的 ASCII 值。value = strtol(number, &end, 16);
再次使用strtol()
函数将输入的字符串number
按照十六进制进行转换。printf("base 16 input, base 10 output: %ld, stopped at %s (%d)\n", value, end, *end);
输出十六进制转换的结果。puts("Next number:");
输出提示信息,要求用户输入下一个数字。puts("Bye!\n"); return 0; }
puts("Bye!\n");
输出 “Bye!” 表示程序结束。return 0;
主函数返回 0,表示程序正常结束。char * s_gets(char * st, int n) { char * ret_val; int i = 0; ret_val = fgets(st, n, stdin);
- 定义了
s_gets
函数,接受一个字符指针st
和一个整数n
作为参数。char * ret_val;
声明一个字符指针用于存储返回值。int i = 0;
声明一个整数变量i
并初始化为 0,用于遍历字符串。ret_val = fgets(st, n, stdin);
调用fgets
函数从标准输入(键盘)读取最多n - 1
个字符并存入st
所指向的字符数组中,返回值赋给ret_val
。if (ret_val) { while (st[i]!= '\n' && st[i]!= '\0') i++; if (st[i] == '\n') st[i] = '\0'; else while (getchar()!= '\n') continue; } return ret_val; }
- 如果
ret_val
非空,表示成功读取了输入。while (st[i]!= '\n' && st[i]!= '\0') i++;
遍历输入的字符串,找到换行符或字符串结束符。- 如果找到换行符(
st[i] == '\n'
),将其替换为字符串结束符'\0'
,以去除换行符。- 如果没有找到换行符,说明输入的字符串长度超过了给定的长度,通过
while (getchar()!= '\n') continue;
不断读取输入直到遇到换行符,以清除输入缓冲区中多余的字符。return ret_val;
返回读取到的字符串的指针,如果读取失败则返回空指针。