C风格字符串
string使用方便,能自动扩展,不用担心内存问题。
string是C 的类,封装了C风格的字符串。
学习C风格字符串可以帮我们搞清楚string的本质,string虽然很方便,但是在某些应用场景中,C风格字符串会更方便,更高效。
大部分的开源库一定有C语言版本,但不一定有C 版本。例如数据库的接口函数,如MYSQL,只有C语言版本,没有C 版本。
在实际开发中,C的库函数和Linux的库函数不可能不用,还有,开源库对C 程序员很重要,可以节省很多时间。
所以如果打算深入的学习C ,必须掌握C风格的字符串。
C语言约定:如果字符型(char)数组的末尾包含了空字符 (也就是0),那么该数组中的内容就是一个字符串。
因为字符串需要用0结尾,所以在声明字符数组的时候,要预留多一个字节用来存放0。
int main()
{ //转为ascii码显示 std::string XYZ = "abc";
std::cout << "str[0]:" << (int)XYZ[0] << std::endl; //97
std::cout << "str[1]:" << (int)XYZ[1] << std::endl; //98
std::cout << "str[2]:" << (int)XYZ[2] << std::endl; //99
std::cout << "str[3]:" << (int)XYZ[3] << std::endl; //0
return 0; }
char name[21]; //这个字符数组我们可以认为 声明了一个存放20个英文字符或10个中文(1个汉字由两个字节来存放,utf-8的汉字由3个字节存放)的字符串。
1、初始化方法
char name[11]; // 可以存放10个字符,没有初始化,里面是垃圾值。
char name[11] ="hello"; // 初始内容为hello,系统会自动添加0。
char name[] = { "hello" }; // 初始内容为hello,系统会自动添加0,数组长度是6。
char name[11] = {"hello" }; // 初始内容为hello,系统会自动添加0。
char name[11] { "hello" }; // 初始内容为hello,系统会自动添加0。C 11标准。
char name[11] = { 0}; // 把全部的元素初始为0
声明字符串,如果没有初始化,危害非常大,远远超过其它数据类型,我们用下边这种没有初始化的方式,有的编译器,会打印烫烫烫烫烫烫烫烫烫烫,有的不会打印,但是会听到系统提示音,这代表没有初始化是有问题的
int main() { char name[11]; std::cout << "name = " << name << std::endl; return 0; }
但是使用后边几种方式初始化,就不会有问题。
2、清空字符串
(1) memset(name,0,sizeof(name)); //把全部元素置为0
(2) name[0] = 0; //不规范,有隐患,不推荐
3、字符串赋值或复制 strcpy() (建议使用strcpy_s(),strcpy()不安全)
char * strcpy(char * dest,char * src);
功能:将src字符串拷贝至dest所指的地址
返回值:返回dest的字符串起始地址
复制完字符串后,会在dest后追加0
如果dest所指的内存空间不够大,会导致数组越界
int main()
{ char name[11];
memset(name, 0, sizeof(char));
char name1[12] = { "hell0000000" };
strcpy(name, name1);
//建议使用strcpy_s(),strcpy()不安全
std::cout << name << std::endl; //这时赋值后就会报错
return 0; }
4、字符串赋值或复制 strncpy() (使用strncpy_s(),strncpy()不安全)
char * strncpy(char * dest,char * src,const size_t n);
功能:把src前n个字符的内容复制到dest中。
返回值:dest字符串起始地址。
如果src字符串长度小于n,则拷贝完字符串后,在dest后追加0,直到n个。
如果src的长度大于等于n,就截取src的前n个字符,不会在dest后追加0。
如果参数dest所指的内存空间不够大,会导致数组的越界。
这里有个坑:
如果不初始化 dest,那么如果复制的值的长度小于dest定义的长度,那么dest后边的内容会是垃圾值,如下所示
但是如果初始化清空后,则是正常的,如下
所以一定要记得初始化值,同样的,使用strcpy_s()函数也要注意。
所有操作字符串的操作,每次操作前都要清空。
5、获取字符串的长度 strlen()
size_t strlen(const char * str);
功能:计算字符串的有效长度,不包含0。
返回值:返回字符串的字符数。
strlen()函数计算的是字符串的实际长度,遇到0结束。
int main()
{ char name[11];
char name1[12] = {"hell000000"};
//memset(name, 0, sizeof(name));
strncpy_s(name, name1,12);
short a = strlen(name);
std::cout << a << std::endl; // 10
return 0; }
6、字符串拼接 strcat() (使用strcat_s() )
char* strcat(char * dest,const char * src);
功能:将src字符串拼接到dest所指的字符串尾部。
返回值:返回dest字符串起始地址。
dest最后原有的结尾字符0会被覆盖掉,并在连接后的字符串的尾部再增加一个0。
如果参数dest所指的内存空间不够大,会导致数组的越界。
int main()
{ char name[11] = { "abcde" };
char name1[12] = {"hell"};
//char name1[12] = { "helllllllll" };
//这种会报错,因为拼接后长度大于11
strcat_s(name, name1);
std::cout << name << std::endl;
return 0;
}
7、字符串拼接 strncat() (使用strncat_s() )
char* strncat(char* dest,const char* src,const size_t n);
功能:将src字符串的前n个字符拼接到dest所指的字符串尾部。
返回值:返回dest字符串的起始地址。
如果n大于等于字符串src的长度,那么将src全部追加到dest的尾部,如果n小于字符串src的长度,只追加src的前n个字符。
strncat会将dest字符串最后的0覆盖掉,字符追加完成后,再追加0。
如果参数dest所指的内存空间不够大,会导致数组的越界。
int main()
{
char name[11] = "abcde";
char name1[12] = {"hell00000"};
strncat_s(name, name1,5);
//strncat_s(name, name1,10); //这种会报错,因为拼接后长度大于11
std::cout << name << std::endl;
return 0; }
8、字符串比较 strcmp() 和 strncmp()
int strcmp(const char* str1,const char* str2);
功能:比较str1和str2的大小。
返回值:相等返回0,str1大于str2返回1,str1小于str2返回-1;
int strncmp(const char* str1,const char* str2,const size_t n);
功能:比较str1和str2前n个字符的大小。
返回值:相等返回0,str1大于str2返回1,str1小于str2返回-1;
两个字符串比较的方法是比较字符的ASCII码的大小,从两个字符串的第一个字符开始,如果分不出大小,就比较第二个字符,如果全部的字符都分不出大小,就返回0,表示两个字符串相等。在实际开发中,程序员一般只关心字符串是否相等,不关心哪个字符串更大或更小。
int main()
{ char str1[4] = "abc";
char str2[5] = "abcd"; // -1
//char str2[5] = "cba"; // -1 , 说明 是一个字符一个字符比较,只要分不出大小继续比较,分出大小则结束并返回
std::cout << strcmp(str1, str2) << std::endl;
std::cout << strncmp(str1, str2,1) << std::endl;
return 0;
}
9、字符串查找 strchr() 和 strrchr()
const char * strchr(const char* s,int c);
返回在字符串s中第一次出现c的位置,如果找不到,返回0。
const char * strrchr(const char* str,int c);
返回在字符串s中最后一次出现c的位置,如果找不到,返回0。
int main()
{ char str[10] = "abcdecfg";
char* ptr = nullptr;
ptr = strchr(str, 'c');
if (ptr != nullptr)
{ std::cout << strchr(str, 'c') << std::endl; //cdecfg
} else
{ std::cout << (int)strchr(str, 'c') << std::endl;
//0 }
ptr = strchr(str, 'h');
if (ptr != nullptr)
{ std::cout << strchr(str, 'h') << std::endl;
//cdecfg
}
else
{ std::cout << (int)strchr(str, 'h') << std::endl;
//0 要进行强转,否则会出现异常
}
//std::cout << strrchr(str, 'c') << std::endl; //cfg
//std::cout << (int)strrchr(str, 'n') << std::endl; //0 要进行强转,否则会出现异常
return 0;
}
10、查找字符串strstr()
const char* strstr(const char* str,const char* substr);
功能:检索子串在字符串中首次出现的位置。
返回值:返回字符串str中第一次出现子串substr的地址;如果没有检索到子串,则返回0。
int main()
{ char str[10] = "abcdefg";
char substr[4] = "cb";
char* ptr = nullptr;
ptr = strstr(str, substr);
if (ptr != NULL)
{ std::cout << strstr(str, substr); //bcefg
}
else
{ std::cout << (int)strstr(str, substr); //0 不进行强转会报错
}
return 0;
}
11、用于string的表达式
可以把C风格的字符串用于包含了string类型的赋值拼接等表达式中。
string aa = "";
char arr[10] = "abcdegf";
aa = arr;
std::cout << aa << std::endl; //abcdegf
12、注意事项
a)字符串的结尾标志是0,按照约定,在处理字符串的时候,会从起始位置开始搜索0,一直找下去,找到为止(不会判断数组是否越界)。
b)结尾标志0后面的都是垃圾内容。
c)字符串在每次使用前都要初始化,减少入坑的可能,是每次,不是第一次。(string好像不用初始化)
d)不要在子函数中对字符指针用sizeof运算,所以,不能在子函数中对传入的字符串进行初始化,除非字符串的长度也作为参数传入到了子函数中。
e)在VS中,如果要使用C标准的字符串操作函数,要在源代码文件的最上面加
#define _CRT_SECURE_NO_WARNINGS (一些的ide环境(如vs2022)好像可以不需要加也可以)