• C primer plus学习笔记 —— 7、字符串


    什么是字符串

    C没有显示的字符串数据类型。
    字符串是以空字符\0结尾的char类型数组。

    定义字符串的三种方式:

    1. 字符串常量(常量存储区)
    2. char类型数组(动态内存)
    3. 指向char的指针(动态内存)

    字符串常量

    "I am a string"
    用双引号括起来的内容就是字符串常量。编译器会在末尾自动加空字符\0
    他属于静态存储类别,也就是该字符串只会被存储一次,在整个程序生命周期内存在。
    printf("s%," "sdf");使用%s在printf中打印字符串转换。
    字符串常量就是指针。

    "xyz"+1; //结果是指向第二个字符y的指针
    *"xyz" //x
    "xyz"[2] //z
    *("xyz"+4) //不可预测,超出了范围。
    
    • 1
    • 2
    • 3
    • 4

    字符串数组

    char words[71] = "I am a string in an array.";
    char wokd[10] = {'c','g','a','/0'};
    char words3[] = "I am a string in an array.";
    
    • 1
    • 2
    • 3
    1. 使用字符串数组要有足够的空间存储字符串。末尾会存在空字符。
    2. 注意第二种方式是标准数组初始化形式,但是如果不加最后空字符则他只是char数组,有了空字符他才可以表示字符串。
    3. 所以我们一般采用第三种方式来让编译器自动计算数组大小。非常方便。

    因为字符串数组和数组一样,所以我们也可以使用指针表示数组首元素地址

    char * p = words;
    p == words == &words[0]
    
    • 1
    • 2

    指向char的指针

    char * pt1 = "Something is pointing at me.";
    char pt2[] = "Something is pointing at me.";
    
    • 1
    • 2

    以上两个声明几乎相同。但是他们字符串的存储位置不同。
    pt1和pt2都是该字符串的首地址。

    不同点
    #define MSG "I'm special"
    int main()
    {
        char ar[] = MSG;
        char *pt = MSG;
        printf("address of \"I'm special\": %p \n", "I'm special"); //0x400660
        printf("          address of MSG: %p\n", MSG);              //0x400660
        printf("              address ar: %p\n", ar);               //0x7ffe88148230
        printf("              address pt: %p\n", pt);               //0x400660
        printf("address of \"I'm special\": %p \n", "I'm special");//0x400660
    
    	//操作字符串常量区
        printf("\n");
        printf("%s \n", MSG);
        pt[2] = '4'; //segment fault
        // printf("%s", MSG);
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    本身字符串是存储在静态常量区,我们打印出他的地址。虽然打印了两次这个字符串,实际程序使用的都是同一个地址,也就是他在静态存储区中只存在一份。
    我们发现字符指针是直接指向这个地址,而字符串数组的首地址并不指向这个地址。

    也就是有:

    1. 数组形式的定义,表示在计算机分配了一个包含29个元素的数组。把静态存储区中的字符串拷贝到数组中。
    2. 字符指针是直接指向这个字符串所在静态存储区中的地址。
    操作常量区字符串

    如果我试图修改常量区的字符串,比如上面代码,修改字符串中某一个字符。pt[2] = '4';会在运行时报段错误。
    所以建议在使用指针指向的字符串时加const防止修改字符串。

    const char *pt = "I'm special";
    
    • 1

    如果要修改原字符串,则不能使用指针表示法,可以使用字符串数组表示法,这样就可以修改字符串的副本。

    定义字符串数组

        const char *mytalents[LIM] = {
            "Adding numbers swiftly",
            "Multiplying accurately", "Stashing data",
            "Following instructions to the letter",
            "Understanding the C language"
        };
        char yourtalents[LIM][SLEN] = {
            "Walking in a straight line",
            "Sleeping", "Watching television",
            "Mailing letters", "Reading email"
        };
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11

    定义字符串数组,也可以使用上述两种方式。
    指针方式:效率更高,因为不用存储字符串副本,但是不能修改原字符串。(绝大多数字符串操作由指针完成)
    数组方式:效率低,但是可以修改字符串。

    字符串IO(输入/输出)

    字符串输入

    在读取字符串时,scanf(%s)只能读取一个单词。而我们常常需要读取一整行输入。

    gets()函数是直接读取一整行。但是他只有一个唯一的参数,无法检查空间是否能装下用户输入的字符数量。所以C99建议不适用这个函数,而C11直接将该函数废弃。

    通常使用fgets()来代替gets()

    #define STLEN 14
    int main(void)
    {
        char words[STLEN];
        puts("Enter a string, please.");
        fgets(words, STLEN, stdin); //标准输入,从键盘读取
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8

    fgets()第二个参数会限制读入字符的最大数量。会读入n-1个字符(存储一个空字符),或者遇到读到遇到的第一个换行符为止。
    fgets会读入输入的换行符,比如输入 apple, 那么words中存储为apple\n\0

    几种字符串输入函数对比

    scanf(),gets(), gets_s(), fgets()。

    • gets() 输入太长会擦除数据
    • gets_s() 当输入不符合预期则处理不太方便
    • fgets()通常作为最佳选择
    • scanf() 会在空白字符(空行,空格,制表符或换行符)就读入停止

    字符串输出

    直接使用puts()函数输出字符串

    字符串函数

    字符串函数都在string.h头文件中

    长度

    size_t strlen(char const *string) 返回值是字符串长度,
    他是一个无符号整数类型。

    不受限制字符串函数

    //复制
    char *strcpy(char *dst, char const *src);//src复制到dst位置,如果两个内存出现重叠则结果是未定义的。所以必须保证dst空间足够,如果src超过dst则结果会有问题。返回值为dst的地址。
    //拼接
    char *strcat(char *dst, char const *src);//src添加到dst后面,返回dst地址。也必须保证dst空间充足不然结果未定义。
    //比较
    int strcmp(char const *s1, char const *s2); //字典比较, 字母顺序比较,如果s1s2则返回值大于0,s1=s2则返回值等于0.
    
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    长度受限制字符串

    对上面函数进行改进,防止难以预料到长字符串在目标数组溢出。

    //复制
    char *strncpy(char *dst, char const *src, size_t len);//src复制到dst位置,向dst只写入len个字符
    //拼接
    char *strncat(char *dst, char const *src, size_t len);//src添加到dst后面,向dst只写入len个字符
    //比较
    int strncmp(char const *s1, char const *s2, size_t len); //字典比较, 字母顺序比较,如果s1s2则返回值大于0,s1=s2则返回值等于0.
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6

    这里注意,如果len长度大于strlen(src),则会拷贝整个src。如果len长度小于src长度,则只拷贝len个字符,而不包括NUL字符结尾。

    字符串查找

    字符串中查找字符

    strchr

    char* strchr(const char* str,char c)
    
    • 1

    功能:查找字符串中第一次出现c的位置
    返回值:返回首次出现c的位置的指针,如果s中不存在c则返回NULL

    strrchr

    char* strchr(const char* str,char c)
    
    • 1

    功能:查找字符串中最后一次出现c的位置
    返回值:返回最后一次出现c的位置的指针,如果s中不存在c则返回NULL

    strpbrk

    char* strpbrk(const char* s1, const char* s2)
    
    • 1

    功能:在字符串s1中寻找字符串s2中任何一个字符相匹配的第一个字符的位置,空字符NULL不包括在内

    查找子串

    strstr

    char* strstr(const char* str,const char* substr)
    
    • 1

    功能:检索子串在字符串中首次出现的位置
    返回值:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回NULL。如果substr为空字符串则返回str

    查找字符串前缀

    strspn

    size_t  strspn(const char* str1, const char* str2)
    
    • 1

    功能:用来计算str1字符串开始部分匹配str2字符串的字符个数
    返回值:返回字符串str1开头连续包含字符串str2内的字符数目。

    如果str所包含的字符都属于str2,那么返回str1的长度
    如果str的第一个字符不属于str2,那么返回0

    strcspn

    size_t  strspn(const char* str1, const char* str2)
    
    • 1

    功能:用来计算str1字符串开始部分不匹配str2字符串的字符个数
    返回值:返回字符串str1开头部分不出现在字符串str2内的字符数目。

    分割字符串

    strtok

    char* strtok(char* str,const char* sep)
    
    • 1

    功能:根据分隔符将字符串分隔成一个个片段
    返回值:返回下一个分割后的字符串指针,如果已无从分割则返回NULL
    说明:

    • sep参数是个字符串,定义了用作分隔符的字符集合
    • 第一个参数指定一个字符串,它包含了一个或者多个由sep字符串中一个或者多个字符分割的标记
    • 第一次调用时将字符串首地址传进去,之后调用不用传地址,内部会有static函数保存指向地址
    • 分隔符不作为输出内容,只做分界符
    • 当strtok在参数s的字符串中发现到参数sep的分割字符时则会将该字符改为’\0’字符
    • 在第一次调用时,strtok必须赋予参数str字符串,往后的调用则将参数s设置成NULL
    • strtok会修改原字符串,所以必须放至栈上
    #include
    #include
    int main() {
    	char str[] = "abc@def.efg";
    	char str1[50] = { 0 };
    	strcpy(str1, str);
     
    	const char* sep = "@.";
    	printf("%s\n", strtok(str1, sep));//打印分隔符分开的第一部分abc
    	printf("%s\n", strtok(NULL, sep));//打印分隔符分开的第二部分def
    	printf("%s\n", strtok(NULL, sep));//打印分隔符分开的第三部分efg
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    格式化字符串

    sprintf()该函数和printf()函数相同,只不过他是把格式化后的字符串放到字符数组中而不是显示到屏幕。
    int sprintf( char *buffer, const char *format [, argument,...] );
    第一个参数就是指向要写入的那个字符串的指针

    int main()
    {
       char str[80];
       float a = 3.14;
       sprintf(str, "Pi 的值 = %f", a);
       puts(str);
       
       return(0);
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    字符串与数字转换

    数字可以以字符形式存储,也可以以数值形式。

    比如213,他可以已字符数组2,1,3,‘0/’的形式存储,也可以以int形式直接存储。

    **C语言在运算的时候只能以数值形式,但是在打印显示的时候要求以字符串形式。**比如printf中我们需要把数值使用%d转换成字符串显示出来,而scanf()是把标准输入的字符串给转换成数值保存到某个变量。

    数字转换成字符串

    1. 使用sprintf()函数,如上所讲。
    2. 使用itoa,用到stdlib,可将整数转化为不同进制数并输出成字符串

    字符串转换成数字

    atoi atof atol
    
    #include 
    #include 
    
    int main(void)
    {
        char* s = "3";
        int count = atoi(s);
        for (int i = 0; i < count; i++)
        {
            printf("%d \n", i);
        }
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    但是这些函数有个问题就是他只把开头的整数转成字符,比如"42rng"他会返回整数42,如果字符串中没有数字,他会返回0.这种未定义的情况可能造成不安全的问题。所以我们可以选择下面的函数。

    strtol strtoul strtod

    long int strtol(const char *str, char **endptr, int base) 把参数 str 所指向的字符串根据给定的 base 转换为一个长整数(类型为 long int 型),base表示进制

    #include 
    #include 
    
    int main(void)
    {
        char str[30] = "2030300 This is test";
        char *ptr;
        long ret;
    
        ret = strtol(str, &ptr, 10); //第一个参数要转换的字符串,第二个参数是转换后的字符串部分,返回值是数值部分
        printf("数字(无符号长整数)是 %ld\n", ret);
        printf("字符串部分是 |%s|", ptr);
        
        return 0;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    注意没有strtoi

  • 相关阅读:
    TensorFlow入门(六、模型的保存和载入)
    docker到底能在哪些平台安装?
    Python--测试代码
    Vue2项目知识点总结-尚品汇
    网关Gateway-快速上手
    04-JS函数
    vue3集成jsoneditor
    系统(层次)聚类
    ROS2知识:编译系统ament_cmake
    ensp基础命令大全(华为设备命令)
  • 原文地址:https://blog.csdn.net/chongbin007/article/details/125958968