【C语言】字符串,补充:单引号和双引号, const和define, 常量指针和指针常量

【C语言】字符串:一串数据类型为字符(char)的数组(字符数组),结束符是''。

一个字符占一个字节的内存,结束符''也占一个字节的内存。

结束符 '' :缩写NUL,空字符。表示字符串的结束。

NULL:空指针。定义在标准库中的值为零的常量。

字符串名就是内存地址,也是第一个字符的内存地址。也可以理解为字符串是指针,指向字符数组。

单个字符:用单引号,例如:'a',  ''。

空字符串或多个字符组成的字符串:用双引号,例如:"good"。

操作字符串(字符数组)的标准库为string.h。

#include <string.h>

0、字符串变量

数组:char 变量名[字符数量] = 字符串;    【字符数量可省略,字符数量包括结束符''】

指针:char *指针变量名;

// 字符数组
char s[6] = "hello";                     // 5个字符 + 1个结束符''
char s[6] = {'h','e','l','l','o',''};  // 单个字符,用单引号

// 指针变量
char *s;
s = "hello";

1、strlen, sizeof

strlen:字符串共有多少个字符(不包括结束符'')。

sizeof:字符串共占多少字节内存(包括结束符'')。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[] = "hello";
    int number = strlen(strings);
    int size = sizeof(strings);
    printf("strings: "%s" has %d elements, memory size is %d bytes",strings, number, size);
    return 0;
}

// 结果为:
strings: "hello" has 5 elements, memory size is 6 bytes

2、memchr,strchar,strrchr  搜索字符串中的字符

memchr:void *memchr(const void *str, int c, size_t n)

参数:str是指定内存区域,c为要搜索的字符(单个字符,需用单引号),n为要搜索的字节数(指定搜索的内存大小)。

返回:指针,指向第一次出现的字符c的内存地址。若没有,返回NULL。

strchar:char *strchr(const char *str, int c)

参数:str是字符串,c为要搜索的字符(单个字符,需用单引号)。

返回:指针,指向第一次出现的字符c的内存地址。若没有,返回NULL。

strrchr:char *strrchr(const char *str, int c)

参数:str是字符串,c为要搜索的字符(单个字符,需用单引号)。

返回:指针,指向最后一次出现的字符c的内存地址。若没有,返回NULL。

 memchr, strchar,strrchr基本用法相同:

1、都可以在字符串中搜索字符。

2、都返回指针,指向搜索到的字符的内存地址。若没有搜索到,返回NULL。

 memchr, strchar,strrchr的区别是:

1、memchr有3个参数:指定内存区域,搜索的字符,搜索的字节数。

      strchar和strrchr只有2个参数:字符串,搜索的字符。

2、memchr和strchr返回的指针都指向第一次出现字符c的内存地址。

     strrchr返回的指针是指向最后一次出现字符c的内存地址。

3、memchr指定搜索的内存大小。

      strchar和strrchr都是字符串结束(结束符'')就停止搜索。

4、memchr可以搜索任意内存区域的字节。

      strchar和strrchr专门搜索字符串中的字符。

// 以 memchr 为例
#include <stdio.h>
#include <string.h>

int main(void)
{
    const char strings[] = "hello";
    char *s;
    s = (char *)memchr(strings, 'l', strlen(strings));
    printf("strings="%s", the first "l" location is %dn",strings, s-strings+1);
    printf("strings="%s", from the first "l" to the end are "%s"",strings, s);
    return 0;
}

// 结果为:
strings="hello", the first "l" location is 3
strings="hello", from the first "l" to the end are "llo"

补充:strstr (字符串中查找子字符串)

strstr:char *strstr(const char *haystack, const char *needle)

在字符串haystack查找子字符串needle。

返回:若找到,返回指针,指向haystack中第一次出现needle的内存地址。若没有,返回NULL。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[] = "hello world wow";
    char *s;
    s = strstr(strings, "world");       // 找到子字符串
    printf("s point to "%s"n",s); 
    return 0;
}

// 结果为:
s point to "world wow"


#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[] = "hello world wow";
    char *s;
    s = strstr(strings, "good");      // 没有找到子字符串
    printf("s point to "%s"n",s); 
    return 0;
}
// 结果为:
s point to "(null)"

3、memcpy,memmove,strcpy,strncpy 复制字符串

memcpy:void *memcpy(void *dest, const void *src, size_t n)

参数:dest是存储复制内容的内存区域(目标字符串),src是要复制的数据源(内存区域),n是复制的字节数。

返回:指针,指向目标字符串。

注意:

memcpy是内存复制,可复制任意类型的数据源。

memcpy指定复制内存大小。不会自动添加结束符''。注意目标字符串中是否有结束符。若没有,需一个字节的内存空间手动添加结束符''。

若目标字符串的内存不够大,可能缓冲区溢出。注意边界检查。

 memmove: void *memmove(void *dest, const void *src, size_t n)

参数:dest是存储复制内容的内存区域(目标字符串),src是要复制的字符串,n是要复制的字节数。

返回:指针,指向目标字符串。

注意:

没有重叠的内存区域时,与memcpy功能相同。

有重叠的内存区域时,复制前会将重叠部分拷贝到目标区域,复制后,源区域的内容会被修改。

// 以 memcpy 为例
#include <stdio.h>
#include <string.h>

int main(void)
{
    char s[] = "hello";                // 数据源“hello”(5个字符+1个结束符'')
    char strings[8];
    memcpy(strings,s,strlen(s)+1);     // 复制字符串(包括结束符'')
    printf("strings = "%s",strings[5] = %pn",strings, strings[5]);   
    return 0;
}

// 结果为:
strings = "hello",strings[5] = 0000000000000000

strcpy:char *strcpy(char *dest, const char *src)

参数:dest是存储复制内容的内存区域(目标字符串),src是要复制的字符串。

返回:指针,指向目标字符串。

注意:

strcpy专门复制字符串,包括结束符''。依赖结束符''来确定复制的终点,注意要复制的字符串需有正确的结束符。

若没有正确的结束符或目标字符串的内存不够,可能缓冲区溢出。注意边界检查。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[6]; 
    // char strings[5];        // warning: '__builtin_memcpy' writing 6 bytes into a region of size 5 overflows the destination [-Wstringop-overflow=]
    strcpy(strings,"hello");   // 复制字符串
    printf("strings = "%s"",strings);   
    return 0;
}

// 结果为:
strings = "hello"

strncpy:char *strncpy(char *dest, const char *src, size_t n)

参数:dest是存储复制内容的内存区域(目标字符串),src是要复制的字符串,n是复制的字节数。

返回:指针,指向目标字符串。

注意:

strncpy指定复制内存大小。

要复制的字符串若小于指定复制的内存大小,则自动填充结束符''。

要复制的字符串若大于等于指定复制的内存大小,则可能需要一个字节的内存空间手动添加结束符'',若复制的内容中有结束符,则无需手动添加。

若目标字符串的内存不够大,可能缓冲区溢出。注意边界检查。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[8];
    strncpy(strings,"hello",8);       // strings 内存存储为:h e l l o   
    printf("strings = "%s"",strings);   
    return 0;
}

// 结果为:
strings = "hello"
#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[6];
    strncpy(strings,"hello",6);       // 无需手动添加结束符,复制内容中已包含
    printf("strings = "%s"",strings);   
    return 0;
}

// 结果为:
strings = "hello"
#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[6];
    strncpy(strings,"helloworld",5);  // 复制原字符串的一部分内容(不包括结束符)
    strings[5] = '';                // 手动添加结束符
    printf("strings = "%s"",strings);   
    return 0;
}

// 结果为:
strings = "hello"
memcpy,strcpy,strncpy的区别
memcpy strcpy strncpy
3个参数:目标字符串,要复制的数据源,复制内存大小 2个参数:目标字符串,要复制的字符串 3个参数:目标字符串,要复制的字符串,复制内存大小
复制内存区域,可复制任意类型的数据源(包括字符串) 专门复制字符串 专门复制字符串
指定复制的内存大小 从要复制的字符串开始到结束符(包括结束符) 指定复制的内存大小

目标字符串中可能手动添加结束符''

注意要复制的字符串中需有正确的结束符'' 目标字符串中可能手动添加结束符''
确保目标字符串的内存足够大 确保目标字符串的内存足够大 确保目标字符串的内存足够大

补充:memset (将某内存区域设置为指定值)

memset:void *memset(void *str, int c, size_t n)

参数:str是存储填充内容的内存区域(目标字符串),c是指定要填充的内容(单个字符,需单引号),n是填充的字节数。

返回:指针,指向目标字符串。

注意:

memset是将指定字符填充到指定内存区域的前n个字节。

用于快速初始化大块内存,或清空内存,或为内存赋值。

memset不做边界检查,需确保目标字符串的内存区域足够大。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[8] = "hihello";
    printf("Before: strings = %sn", strings);
    memset(strings, '#', 2);                    // 将字符数组前2个字符设为'#'
    printf("After: strings = %sn", strings);
    memset(strings, '', sizeof(strings));     // 将字符数组清零,使用''代替0
    printf("End: strings = %sn", strings);
    return 0;
}

// 结果为:
Before: strings = hihello
After: strings = ##hello
End: strings =

4、strcat,strncat 字符串结尾追加内容

注意:确保目标字符串的内存区域必须足够大,能够容纳追加的内容。

从目标字符串的结束符''开始追加,''被第一个追加的字符替换,追加完会加上结束符''。

strcat:char *strcat(char *dest, const char *src)

参数:dest是目标字符串,src是要追加的内容。

返回:指针,指向目标字符串。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[16] = "hello";
    printf("Before: strings = %sn", strings);
    strcat(strings, "world");         // 追加内容
    printf("After: strings = %sn", strings);
    return 0;
}

// 结果为:
Before: strings = hello
After: strings = helloworld

strncat:char *strncat(char *dest, const char *src, size_t n)

参数:dest是目标字符串,src是要追加的内容,n是追加的字节数。

返回:指针,指向目标字符串。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char strings[16] = "hello";
    printf("Before: strings = %sn", strings);
    strncat(strings, "world", sizeof("world"));         // 追加内容(指定字节数)
    printf("After: strings = %sn", strings);
    return 0;
}

// 结果为:
Before: strings = hello
After: strings = helloworld

5、memcmp,strcmp,strncmp  比较字符串

memcmp: int memcmp(const void *str1, const void *str2, size_t n)

将两个内存区域str1和str2的前n个字节数进行比较。

返回:若str1>str2,返回值>0;若str1=str2,返回值=0;若str1<str2,返回值<0。

strcmp:int strcmp(const char *str1, const char *str2)

将两个字符串str1和str2进行比较。

返回:若str1>str2,返回值>0;若str1=str2,返回值=0;若str1<str2,返回值<0。

strncmp:int strncmp(const char *str1, const char *str2, size_t n)

将两个字符串str1和str2的前n个字节数进行比较。

返回:若str1>str2,返回值>0;若str1=str2,返回值=0;若str1<str2,返回值<0。

memcmp,strcmp,strncmp
memcmp strcmp strncmp
比较两个内存区域(可以字符串,但通常用于比较结构体或数组等) 比较两个字符串,整个字符串比较 比较两个字符串,指定比较长度
指定比较的字节数 / 指定比较的字节数
碰到不同的字符或比较完限定的字节数就停止 碰到不同的字符或结束符''就停止 碰到不同的字符或结束符''或比较完限定的字节数就停止
// 以 strcmp 为例
#include <stdio.h>
#include <string.h>

int main(void)
{
    char s[] = "Hello";
    if(strcmp(s, "hello") == 0)       // 比较字符串
    {
        printf("the same string");
    }
    else
    {
        printf(""%s" and "hello" are different", s);
    }
    return 0;
}

// 结果为:
"Hello" and "hello" are different

6、匹配字符串中的字符是否在另一个字符串中出现

strspn(str1,str2):str1中第一个不在str2的字符下标。

strcspn(str1,str2):str1中从开头连续多少字符不在str2中。

strpbrk(str1,str2):str1中第一个在str2中的字符,返回指针,指向str1中匹配到的字符。

strspn:size_t  strspn ( const  char *str1,  const  char  *str2)
strcspn:size_t  strcspn ( const  char  *str1,  const  char  *str2)
strpbrk:char  *strpbrk ( const  char  *str1,  const  char  *str2)
#include <stdio.h>
#include <string.h>

int main(void)
{
    char s1[] = "hellogoodworld";
    char s2[] = "good";
    int j = strspn(s1,s2);            // s1中第一个不在s2中的字符下标
    printf(""%s" , the first char not in "%s" is index %dn", s1, s2, j);
    int k = strcspn(s1,s2);           // s1中从开头连续多少字符不在s2中
    printf(""%s" , %d char not in "%s"n", s1, k, s2);
    char *x = strpbrk(s1,s2);          // s1中第一个在s2中的字符
    printf(""%s" , the first char in "%s" is %cn", s1, s2, *x);
    return 0;
}

// 结果为:
"hellogoodworld" , the first char not in "good" is index 0
"hellogoodworld" , 4 char not in "good"
"hellogoodworld" , the first char in "good" is o

7、与LC_COLLATE设置相关

strxfrm(目标字符串, 源字符串, 转换的字节数n):将源字符串根据当前LC_COLLATE转换n个字节,再放置到目标字符串指向的内存区域中。返回被转换的字符串的长度(不包括结束符)。

strxfrm 转换字符串格式,便于后续字符串比较。需确保有足够的内存空间存放转换后的字符串。

strcoll(str1,str2):比较两个字符串。返回int类型的值(str1>str2,返回值>0;str1=str2,返回值=0;str1<str2,返回值<0)。

strcoll 根据LC_COLLATE环境变量设置的字符排列次序来比较,若LC_COLLATE设置为"POSIX"或者"C",strcoll 函数与strcmp 函数完全相同。

strxfrm:转换字符串格式。size_t  strxfrm ( char  *dest,  const  char  *src,  size_t  n)
strcoll:比较字符串。int  strcoll ( const  char  *str1,  const  char  *str2)

通过 标准库locale.h 中的 setlocale 来设置 LC_COLLATE 。

获取或设置地域化信息:char *setlocale(int category, const char *locale) 

8、strerror(错误号):通过错误号返回对应的错误信息。

  • 当使用系统调用或其它库函数时,如果发生错误,通常由整数变量errno(标准库errno.h中通过系统调用设置的宏) 记录错误号。
  • 可使用strerror将错误号转换为对应的错误信息字符串。
  • char *strerror(int errnum)   【参数:错误号。返回指针,指向对应的错误信息字符串】
  • 具体错误信息取决于开发平台和编译器。
  • strerror不应在多线程环境中使用。

9、strtok 将字符串分割

strtok:char *strtok(char *str, const char *delim)

参数:str是要分割的字符串,delim是分割符。

返回:指针,指向被分割的第一个子字符串。若没有可检索的字符串,返回空指针。

获取下一个子字符串:继续调用strtok函数。strtok(NULL,分割符);

注意:strtok函数会改变被操作的字符串,最好拷贝。

#include <stdio.h>
#include <string.h>

int main(void)
{
    char s[] = "hello world wow";
    printf("Before: s = "%s"n",s);

    char *x;
    x = strtok(s, " ");            // 使用空格" "来分割字符串
    while(x != NULL)
    {
        printf("%sn",x);
        x = strtok(NULL, " ");     // 获取下一个子字符串
    }
    
    printf("After: s = "%s"n",s);
    return 0;
}

// 结果为:
Before: s = "hello world wow"
hello
world
wow
After: s = "hello"

补充:

1、单引号、双引号

单引号:只能一个字符(占用一个字节)。不能用于空字符串和多个字符组成的字符串。

双引号:0到多个字符组成的字符串,可用于空字符串。若双引号中只有一个字符,则占用两个字节(一个字符和结束符'')。

例如:
char ch = 'A';          // 一个字符'A', 占用一个字节

char s[] = "A";         // 一个字符"A"和结束符'', 占用两个字节
char s[] = "hello";     // 五个字符"hello"和结束符'', 占用六个字节
char s[] = "";          // 空字符串, 空字符(结束符)'',占用一个字节

输出单引号和双引号:使用转义字符 ' 和 "  。

#include <stdio.h>

int main(void)
{
    printf(""hello",the first char is 'h'");   
    return 0;
}

// 结果为:
"hello",the first char is 'h'

2、const、define

const:定义常量,告诉编译器某变量的值在初始化后不能修改。

可以修饰变量、数组、指针、函数参数、函数返回值等。

const int a = 10;                  // 变量a的值不能被修改

const char s[] = "hello";          // 数组只读,不能被修改

const int *ptr = &a;               // 常量指针,指向a的内存地址。指针可修改,指针指向的内存地址的内容不能修改

int *const ptr = &a;               // 指针常量,指向a的内存地址。指针不可修改,指针指向的内存地址的内容可修改。

int fun1(const int m)              // 函数中传入的参数m不能被修改
{
    ...
}

const fun1(void)                    // 函数的返回值不能被修改
{
    return "hello";
}

 define:预处理指令。#define 又称宏定义。定义一个标识符来表示常量。在预处理阶段就将标识符出现的地方文本替换为常量值。

#define  常量名(大写字母)  常量值

#include <stdio.h>
#define NUMBER 10       // 定义常量NUMBER

int main(void)
{
    const int k = 8;    // 定义常量k
    // k = 5;           // 常量不能修改。error: assignment of read-only variable 'k'
    printf("[  const  ] k = %dn", k);
    // NUMBER = 2;      // 预处理阶段已经文本替换。不能作为变量修改。error: lvalue required as left operand of assignment
    printf("[ #define ] NUMBER = %dn", NUMBER);
    return 0;   
}

// 结果为:
[  const  ] k = 8
[ #define ] NUMBER = 10

const和define的区别:

1、const在编译和运行时都有效。define只在预处理阶段有效。

2、const占用存储空间。define不占用存储空间。

3、const有明确的类型信息,需要类型检测。define是宏定义,没有类型,只是文本替换。

4、const可在调试时查看和修改。define在预处理阶段已通过文本替换的方式插入常量的值,在调试时很难追踪。

5、const遵循作用域,可以是局部也可以是全局。define默认是全局。

6、const定义后不能取消,可通过extern在其它地方重新定义。define可以通过#undef预处理器指令取消之前的宏定义。

3、常量指针、指针常量

常量指针:指针指向的内存地址中的内容不能修改,但指针指向的内存地址可以改变。

常量指针:指向可修改,内容不能改

const 基类型 *常量指针名;              // 或者 基类型 const *常量指针名; 

常量指针名 = 内存地址;                  // 修改指针的指向

#include <stdio.h>

int main(void)
{
    int x = 10;
    int y = 8;
    const int *n = &x;       // 常量指针,初始化指向x的内存地址
    printf("Before: n point to %p, content is %dn", n, *n);
    n = &y;            // 修改指针的指向,指向y的内存地址
    // *n = 5;         // 不能修改指向的内存地址中的内容。报错:error: assignment of read-only location '*n'
    printf("After: n point to %p, content is %dn", n, *n);
    y = 6;             // 通过引用修改指向的内存地址中的内容。指针指向y的内存地址,不能通过*n修改值,但可修改y的值
    printf("End: n point to %p, content is %dn", n, *n);
    return 0;
}

// 结果为:
Before: n point to 000000000061FE14, content is 10
After: n point to 000000000061FE10, content is 8
End: n point to 000000000061FE10, content is 6

指针常量:指针指向的内存地址不能修改,但指向的内存地址的内容可以改变。

                  指针常量必须初始化,一旦初始化,指针的指向便不能修改。

指针常量:指向初始化后不能改,内容可修改

基类型 *const 指针常量名 = 初始化值;         // 指针常量必须初始化

*指针常量名 = 内存地址中的内容;                // 修改指向的内存地址的内容

#include <stdio.h>

int main(void)
{
    int x = 10;
    int y = 8;
    int *const n = &x;     // 指针常量,必须初始化,一旦初始化,不能修改指针的指向
    printf("Before: n point to %p, content is %dn", n, *n);
    // n = &y;             // 不能修改指针的指向。报错:error: assignment of read-only variable 'n'
    *n = 5;                // 修改指向的内存地址中的内容
    printf("After: n point to %p, content is %dn", n, *n);
    return 0;
}

// 结果为:
Before: n point to 000000000061FE0C, content is 10
After: n point to 000000000061FE0C, content is 5

本图文内容来源于网友网络收集整理提供,作为学习参考使用,版权属于原作者。
THE END
分享
二维码
< <上一篇
下一篇>>