C语言字符串的例子,你真的都会了吗

731 阅读12分钟

概览

  1. 串联起来的字符串字面量、相当于指针、字符串数组与指针指向const
  2. 存储多个不同的字符串:指针表示与数组表示
  3. gets、fgets、fputs、puts、gets_s、putchar、getchar、scanf、printf对比
  4. 空字符、空白符、EOF、空指针NULL
  5. 字符串函数:strlen、strcat、strncat、strcmp、strncmp、strcpy、strncpy、sprintf、strchr、strrchr、strpbrk、strstr
  6. ctype.h与字符相关的函数
  7. 命令行参数,如果参数是整数

示例

  1. 串联起来的字符串字面量、相当于指针、字符串数组与指针指向const
#include<stdio.h>
int main(void){
    // 串联起来的字符串字面量(常量),""之间是空白符即可,如果中间断开,要注意缩进的影响
    printf("你好" " 谁说不是呢?"
    " 听,梦的声音"
       "好棒啊\n"
    );
    printf("你好\
吗");
}

// 结果
// 你好 谁说不是呢? 听,梦的声音好棒啊
// 你好吗
#include<stdio.h>
int main(void){
    // 字符串是以空字符结尾的char型数组,于是数组与指针的关系就可应用于此
    char a[] = {'a','b','c','\0'};
    // 注意c是常量而c中元素是变量,b是变量
    char *b  = "abc";  // 指针指向字符串常量的地址
    char c[] = "abc";  // 数组的每个元素被初始化为字符串常量对应的字符,字面量存储在静态内存中,该数组运行时创建
    printf("%s %p\n",a,a);
    printf("%s %p\n",b,b);
    printf("%s %p\n",c,c);
    printf("%s %p\n","abc","abc");
    // 字符串数组与指针
    char d[][8] = {"这是1","这是2","这是3"};  // 字符数组
    char *e[8] = {"这是1","这是2","这是3"};  // 指针数组,数组中每个指针指向对应的字符串字面量
    // 下面的这个非常容易错,不能是单纯的**因为这样的对象是1个字节的char,指针++怎么弄?编译就会出问题。然后还必须明确这是数组指针!
    char (*f)[8] = (char [][8]){"这是1","这是2","这是3"};  // 必须是一个数组指针,指向一个字符串数组复合字面量
    printf("%s %s %s %p %p\n",d[0],d[1],d[2],d,d[1]);
    printf("%s %s %s %p %p\n",e[0],e[1],e[2],e,e[1]);
    printf("%p\n","这是2");
    printf("%s %s %s %p %p\n",f[0],f[1],f[2],f,f[1]);  // 复合字面量作为一个整体
}
// 结果
// abc 0x7ffee536d5e4
// abc 0x10a892f62
// abc 0x7ffee536d5d4
// abc 0x10a892f62
// 这是1 这是2 这是3 0x7ffee536d640 0x7ffee536d648
// 这是1 这是2 这是3 0x7ffee536d600 0x10a892f75
// 0x10a892f75
// 这是1 这是2 这是3 0x7ffee536d5e8 0x7ffee536d5f0

#include<stdio.h>
#include<string.h>
int main(void){
    // 字符串与const指针
    // char a[] = "测试";
    char a[] = "abc";
    const char *p =  a; // 数组名代表数组首元素的地址
    // printf("%lu",strlen(a)); // 对于”测试“,结果是6,因此想要单独输出一个汉字,就应该再弄一个二维数组,因为一个汉字对应两个char啊
    printf("%c",*p); // 正常输出a
    *p = '0';
}

// 结果
// test.c:10:8: error: read-only variable is not assignable
//     *p = '0';
//     ~~ ^
// 1 error generated.
  1. 存储多个不同的字符串:指针表示与数组表示

见上一条的字符串数组与指针:

char d[][8] = {"这是1","这是2","这是3"};  // 字符数组
char *e[8] = {"这是1","这是2","这是3"};  // 指针数组,数组中每个指针指向对应的字符串字面量
    // 下面的这个非常容易错,不能是单纯的**因为这样的对象是1个字节的char,指针++怎么弄?编译就会出问题。然后还必须明确这是数组指针!
char (*f)[8] = (char [][8]){"这是1","这是2","这是3"};  // 必须是一个数组指针,指向一个字符串数组复合字面量
  1. gets、fgets、fputs、puts、gets_s、putchar、getchar、scanf、printf对比
char * gets(char *)
char * fgets(char *__restrict__, int, FILE *)
int fputs(const char *__restrict__, FILE *__restrict__)
int	 getchar(void);
int	 scanf(const char * __restrict, ...) __scanflike(1, 2);
  • gets 已经被C11废弃了(编译器会有warning: this program uses gets(), which is unsafe.)。gets 是读入一行数据,而且不管你的存储位置够不够,不够的话接着往后写,就产生了安全性问题了,这是未定义的。对应puts打印字符串,并在结尾加上"\n",gets 是直接丢弃"\n"的(不放回输入缓冲)。
  • fgets 经常被用于代替 gets ,它有三个参数,第一个是存储位置的指针,第二个是最多读取到的字符数,第三个是从什么位置读取(文件),通过第二个参数的限制,能够解决缓冲区溢出overflow问题。对应的fputs打印字符串,与puts不同的是它直接打印不会多加"\n",因此fgets会直接把"\n"存储到字符数组中而不是丢弃,另外fputs还需要指定第二个参数文件(如果是屏幕的话就是stdout)。它读到文件末尾会返回空指针:NULL
  • gets_s 是一个可选的扩展,C11新增,编译器不一定实现了它,它用一个参数限制了读入的字符数,只从标准输入读取,因此只有两个参数,另外换行符会丢弃,然后还会丢去后续知道换行,并且需要处理函数。
  • getchar是单个字符读取函数,返回字符,没有参数。对应putchar打印单个字符到屏幕。它读到文件末尾会返回:EOF,其值为-1,定义在stdio.h中。
  • scanf非常全能的输入函数,能读取单词,对于字符串有格式说明符"\s",但是只能一个单词,它以空白符分隔单词,并且后面的字符不丢弃直接送回缓冲区。和gets一样,溢出就会造成abort中止,因此要注意分配足够的空间。之前已经探讨过输入输出缓冲区的问题了,C语言的输入采取缓冲的策略,键盘输入是行缓冲输入,在按下ENTER后才刷新缓冲区,这样便于你输错了还可以更改。它读到文件末尾会返回:EOF,其值为-1,定义在stdio.h中。
#include<stdio.h>
#define MAXSIZE 10
int main(void){
    char a[MAXSIZE];
    char b;
    // fgets 函数
    fgets(a,MAXSIZE,stdin);
    fputs(a,stdout);
    b = getchar();
    putchar(b);
}

// 结果
// hello,it's me,do you know?
// hello,it's% 
  1. 空字符、空白、EOF、空指针NULL
  • 空字符(null character),C语言用它标记字符串的结束:\0,其ASCII码值为(等价于)0,但不是数字0,它是非打印字符。C中的字符串一定以它结束。
  • 空白:在 ASCII 编码中,这样的空白符包括空格' '、水平制表符'\t'、换行符'\n'、垂直制表符'\v'、换页'\f'以及回车'\r'。
  • EOF:定义于stdio.h,其值为-1,用于getcharscanf等返回数字的函数读取文件读到文件末尾时返回。为什么是-1?因为getchar返回值通常介于0-127,如果系统支持扩展字符集的话就是0-255,无论哪种情况-1都不对应任何字符。
  • 空指针(null pointer):NULL,用于fputs等需要返回char型指针的函数读到文件结尾时返回。以mac为例,被定义为#define __DARWIN_NULL ((void *)0),是一个void类型的指针0,一般可以用数字0代替,NULL定义在_null.h中,__DARWIN_NULL定义在_types.h文件中。该指针保证不会指向有效数据。
  1. 字符串函数:strlen、strcat、strncat、strcmp、strncmp、strcpy、strncpy、sprintf、strchr、strrchr、strpbrk、strstr
size_t	 strlen(const char *__s)
char *strcat(char *s1, const char *s2)
char *strncat(char *s1, const char *s2, size_t n)
int	 strcmp(const char *__s1, const char *__s2)
int	 strncmp(const char *__s1, const char *__s2, size_t __n)
char *strcpy(char *dst, const char *src)
char *strncpy(char *dst, const char *src, size_t n)
sprintf(str, __VA_ARGS__)
const char* strchr(const char* __s, int __c)
const char* strrchr(const char* __s, int __c)
const char* strpbrk(const char* __s1, const char* __s2)
const char* strstr(const char* __s1, const char* __s2)
char * strpbrk(const char * cs,const char * ct)
{
    const char *sc1,*sc2;
    for( sc1 = cs; *sc1 != '\0'; ++sc1)
    {
        for( sc2 = ct; *sc2 != '\0'; ++sc2)
        {
            if (*sc1 == *sc2)
            {
                return (char *) sc1;
            }
        }
    }
    return NULL;
}
#include<stdio.h>
#include<string.h>
#define MAXSIZE 100
int main(void){
    char a[MAXSIZE] = "testA";
    char b[] = "testB";
    // strlen 字符串长度,不包含空字符
    printf("1: %zd\n",strlen(b));
    // strcat 字符串连接,注意要保证dest的空间是足够的,否则会abort
    printf("2: %s %s\n",strcat(a,b),strcat(a,b)); // 注意函数调用完毕后再打印,所以两个都是一样的最终结果
    // strncat 字符串带最大长度拼接(防止溢出)
    printf("3: %s\n",strncat(a,"hello",3));  // testAtestBtestBhel
    // strcmp 字符串按ASCII值比较,在前为-1(或说前者值小的为-1),相等为0,在后为1(或说前者值大的为1)
    printf("4: %d\n",strcmp(a,b));  //-1,注意比较的是字符串而非字符
    // strncmp 字符串前n个字符比较,是strcmp的升级
    printf("5: %d\n",strncmp(a,b,1));  //0,即是不是相同的开头,也可以用字符串常量看一个字符串是不是指定的开头
    // strcpy 字符串复制,相当于”赋值“
    strcpy(a+1,b);  // 直接打印它的结果仍是testB,因为函数返回的是第一个参数的地址
    printf("6: %s\n",a);  //ttestB
    // strncpy 字符串带最大长度复制(防止溢出)
    strncpy(a,b+1,2);  // 直接打印它的结果esestB,不是想要的,因为超过了2,导致没有复制到b的'\0'字符!
    a[2] ='\0'; // 需要手动设置空字符
    printf("7: %s\n",a);  //es
    // sprintf 打印到字符串(相当于重写复制),声明在stdio.h中,第一个参数为dest,其余同printf
    sprintf(a,"nihao %s " "hello",b);  //nihao testB hello,注意不要形成自己打印到自己,会出现意想不到的结果
    printf("8: %s\n",a);  //es nihao testB hello
    // strchr 查找字符,返回找到的第一个字符的位置的指针,没找到返回空指针
    printf("9: %s\n",strchr(a,'s'));  //stB hello
    // strrchr 查找字符,返回找到的最后一个字符的位置指针,没找到返回空指针
    printf("10: %s\n",strrchr(a,'l'));  //lo
    // strpbrk 找出最先含有搜索字符串中任一字符的位置并返回,没找到返回空指针
    printf("11: %s\n",strpbrk(a,"abc")); //ao testB hello
    // strstr 查找字符串,返回最先找到的字符串的首地址,没找到返回空指针
    printf("12: %s\n",strstr(a,"test"));  // testB hello
}   

// 结果
// 1: 5
// 2: testAtestBtestB testAtestBtestB
// 3: testAtestBtestBhel
// 4: -1
// 5: 0
// 6: ttestB
// 7: es
// 8: nihao testB hello
// 9: stB hello
// 10: lo
// 11: ao testB hello
// 12: testB hello
  1. ctype.h与字符相关的函数

参见:C语言函数大全之 ctype.h

// 返回值都为int,返回值为非零(真),零(假)

isalnum(int _c)  //当c为数字0-9或字母a-z及A-Z时,返回非零值,否则返回零
isalpha(int _c)  // 若为英文字母,返回非0(小写字母为2,大写字母为1)。若不是字母,返回0。

/*
isblank() 专指那些用来分割一行文本中的单词(文字)的空白符,不能换行换页,或者有其它特殊效果。
isspace() 没有这些要求,它指代所有的空白符。
也就是说,isblank() 所指的空白符是 isspace() 的一个子集。
*/
isblank(int _c)  // 检测空白符:'\t'和''(空格)
isspace(int _c)  // 检测所有空白符

islower(int _c)  //检测一个字符是否是小写字母
isupper(int _c)  //检测一个字符是否是大写字母

isprint(int _c)  //检测一个字符是否是可打印字符(Printable Character)
iscntrl(int _c) //检测一个字符是否是控制字符(Control Character),与可打印字符相对。

isdigit(int _c)  //检测一个字符是否是十进制数字
isxdigit(int _c) //检测一个字符是否是十六进制数字

ispunct(int _c)   //检测一个字符是否是标点符号

// 如果转换成功,那么返回与 _c 对应的字母;如果转换失败,那么直接返回 _c(值未变)。
tolower ( int _c )  //将大写字母字符转换为小写字母字符
toupper(int _c)  //将小写字母转换为大写字母
#include<stdio.h>
#include<ctype.h>
int main(void){
    // ctype 字符处理函数
    printf("isalnum: %d\n",isalnum('1'));  //1
    printf("isalpha: %d\n",isalpha('1'));  //0

    printf("isdigit: %d\n",isdigit('1'));  //1
    printf("isxdigit: %d\n",isxdigit('1'));  //1

    printf("isprint: %d\n",isprint('1'));  //1
    printf("iscntrl: %d\n",iscntrl('1'));  //0

    printf("isupper: %d\n",isupper('a'));  //0
    printf("islower: %d\n",islower('a'));  //1

    printf("isblank: %d\n",isblank('\n'));  //0
    printf("isspace: %d\n",isspace('\n'));  //1
}   

// 结果
// isalnum: 1
// isalpha: 0
// isdigit: 1
// isxdigit: 1
// isprint: 1
// iscntrl: 0
// isupper: 0
// islower: 1
// isblank: 0
// isspace: 1
  1. 命令行参数,如果参数是整数,怎么转换?

命令行参数(command-line argument),可以在main函数中传入两个参数,第一个是命令行中字符串的数量int argc,另一个是命令行中所有字符串的列表char * argv[](系统用空格表示一个字符串的结束和下一个字符串的结束)。

关于字符串转数值型数据,需要用到stdlib.h中声明的函数:

// 返回转换后的值
double	 atof(const char *);  //将字符串转换成一个双精度数值
int	 atoi(const char *);  // 将字符串转换成一个整数值
long	 atol(const char *);  //将字符串转换成一个长整数
long long    atoll(const char *);  //将字符串转换成一个长长整数

// 第一个参数是待转换字符串地址,第二个参数是一个指针的地址,标识输入数字结束字符的地址,base表示了以什么进制写入。
double	 strtod(const char *, char **) __DARWIN_ALIAS(strtod);
float	 strtof(const char *, char **) __DARWIN_ALIAS(strtof);
long	 strtol(const char *__str, char **__endptr, int __base);
long double
	 strtold(const char *, char **);
#if !__DARWIN_NO_LONG_LONG
long long 
	 strtoll(const char *__str, char **__endptr, int __base);
#endif /* !__DARWIN_NO_LONG_LONG */
unsigned long
	 strtoul(const char *__str, char **__endptr, int __base);
#if !__DARWIN_NO_LONG_LONG
unsigned long long
	 strtoull(const char *__str, char **__endptr, int __base);

ato没有错误检测功能,非法行为的结果是未定义的,而strto是有错误检测功能的,因此使用strto更安全。

#include<stdio.h>
#include<stdlib.h>
int main(int argc, char * argv[]){
    char * end;
    printf("字符串的总数:%d\n",argc);  
    // argv[0] 是命令,argv[1] 是第一个参数,以此类推
    printf("第一个输入的整数: %d\n",atoi(argv[1])+1);
    printf("第二个输入的双精度浮点数: %f\n",atof(argv[2])+1.0f);
    printf("第三个输入的双精度浮点数: %f\n",strtod(argv[3],&end)+1);
    printf("第四个输入的单精度浮点数: %f\n",strtof(argv[4],&end)+1.0);
}   

// 结果示例
// ./test 1 1.55 1.55 1.55
// 字符串的总数:5
// 第一个输入的整数: 2
// 第二个输入的双精度浮点数: 2.550000
// 第三个输入的双精度浮点数: 2.550000
// 第四个输入的单精度浮点数: 2.550000