一、概念
1 复合数据类型
字符串是一种复合数据类型,是基本数据类型中的字符与指针或者数组的复合。
2 一个字符串以什么为结束标志?
'\0'
(二是字符串常量,三是栈上的字符数组,四是堆上的字符串,注意从1 定义、2 赋值、3 sizeof三个方面对其进行对比)
二、使用字符串字面值赋给char*——字符串常量
1 定义:
使用如下方式定义的字符串是常量,在常量区:
#include <stdio.h>
#include <string.h>
int main() {
// 见如下三种定义字符串常量的方式
// char* str_con1 = {'h', 'e', 'l', 'l', 'o', '\0'}; // 这种定义方式不行
char* str_con2 = {"hello"}; // 不常见
char* str_con3 = "hello"; // 定义字符串常量的主要方式
// 证明是常量
*(++str_con2) = 'y';
printf("Can str_con2 be altered? ------- %s\n", str_con2);
*(++str_con3) = 'y';
printf("Can str_con3 be altered? ------- %s\n", str_con3);
return 0;
}
上面这段代码编译可以通过,但是运行会报段错误,这证明了以上述方式定义出的字符串是常量,常量区的数据一经定义不可再变。
2 上述代码中我们对定义的字符串常量都初始化了,如果不初始化而后赋值呢?
#include <stdio.h>
#include <string.h>
int main() {
char* str_con;
str_con = "hello";
printf("str_con is %s\n", str_con);
return 0;
}
这里充分说明了一个道理(这个知识点是本文最重要的知识点,是理解字符串的关键):
字符串的字面值是常量。
也就是说,只要我们在代码里写字符串字面值,这个字符串它就是常量,它就在常量区,只不过我们虽然不能改变常量,但是可以将这个常量复制到堆上或者栈上。
3 sizeof:8
#include <stdio.h>
int main() {
char* s = "hello world!";
printf("%ld\n", sizeof(s));
return 0;
}
三、使用数组实现字符串——字符数组
1 定义:
使用如下方式定义字符数组,字符数组不在常量区,在栈区,可随意更改:
#include <stdio.h>
#include <string.h>
int main() {
// 见如下三种定义方式:
char str_var1[] = {' ', 'w', 'o', 'r', 'l', 'd', '!', '\0'}; // 必须写上'\0',系统不会为这种定义出来的字符串添加'\0'
char str_var2[] = {" world!"}; // 不常见
char str_var3[] = " world!"; // 定义字符数组的主要方式,这里其实就是我们将一个字符串常量复制到了栈上
// 证明不是常量
str_var1[0] = '1';
printf("Can str_var1 be altered? ------- %s\n", str_var1);
str_var2[0] = '2';
printf("Can str_var2 be altered? ------- %s\n", str_var2);
str_var3[0] = '3';
printf("Can str_var3 be altered? ------- %s\n", str_var3);
return 0;
}
2 上述代码中我们对定义的字符数组都初始化了,如果不初始化而后赋值呢?
#include <stdio.h>
#include <string.h>
int main() {
char str_var[100];
memset(str_var, '\0', 100);
str_var = "hello world!";
printf("str_var is %s\n", str_var);
return 0;
}
编译未通过,说明字符串常量无法在非初始化的情况下复制给一个栈上的字符数组:
那么如何解决这个问题呢?——使用字符串操作函数。
3 sizeof:3
#include <stdio.h>
int main() {
char s[3] = {'s', 'a', '\0'}; // 字符数组
printf("%ld\n", sizeof(s));
return 0;
}
四、使用堆实现字符串——字符串(应用最频繁的情况)
1 定义:
在堆区。
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char* str_var = (char*)malloc(100);
memset((void*)str_var, '\0', 100);
char* str_hw = (char*)memcpy((void*)str_var, "hello world!", strlen("hello world!"));
*(++str_hw) = '0';
printf("Can str_var be altered? ------- %s\n", str_var);
return 0;
}
2 上述代码中我们对定义在堆上的字符串采用了memcpy进行初始化,如果不这样初始化而直接赋值呢?(这是一种很搞笑的情况)
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char* str_var = (char*)malloc(100);
memset((void*)str_var, '\0', 100);
str_var = "hello world!";
printf("str_var is %s\n", str_var);
// free(str_var);
return 0;
}
让我们来分析一下上述情况:
我们先是使用malloc在堆上申请了100个字节的内存空间并对此置0,而后搞笑的事情发生了:我们使用了原本指向malloc申请的内存的指针指向了一个常量区的字符串常量,就此该程序发生了内存泄露,我们接下来将无法找到malloc申请的内存,free(str_var)将会报错,因为free的并不是malloc申请的内存而是常量区的内存,可是常量区的内存怎么能用free去释放呢?
3 sizeof:8
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
int main() {
char* s = (char*)malloc(100);
memset((void*)s, '\0', 100);
(char*)memcpy((void*)s, "hello world!", strlen("hello world!"));
printf("%ld\n", sizeof(s));
return 0;
}
五、<string.h>常用函数
在认识常用函数之前,先说清楚一件事:<string.h>的所有函数都不管'\0'!
1 测定字符串长度:
函数原型:size_t strlen(const char *str);
返回值:无符号长整型(%ld)
参数:需要测定长度的字符串
2 字符串复制函数:(注意:dest要大于等于src,要不会造成内存溢出)
函数原型:char* strcpy(char* dest, const char* src);
返回值:指向了dest的指针
参数:dest 目标内存
src 源字符串
函数原型:char* strncpy(char* dest, const char* src, size_t n);
返回值:指向了dest的指针
参数:dest 目标内存
src 源字符串
n 要从src复制的字符数
函数原型:void* memcpy(void* dest, const void* src, size_t n);
返回值:指向了dest的指针
参数:dest 目标内存
src 源字符串
n 要从src复制的字符数
函数原型:void* memmove(void* dest, const void* src, size_t n);
返回值:指向了dest的指针
参数:dest 目标内存
src 源字符串
n 要从src复制的字符数
3 字符串比较函数:
函数原型:int memcmp(const void* str1, const void* str2, size_t n)
返回值:如果返回值 < 0,则表示 str1 小于 str2
如果返回值 > 0,则表示 str1 大于 str2
如果返回值 = 0,则表示 str1 等于 str2
参数:str1 第一个字符串
str2 第二个字符串
n 要比较的字符个数
函数原型:int strcmp(const char* str1, const char* str2);
返回值:如果返回值 < 0,则表示 str1 小于 str2
如果返回值 > 0,则表示 str1 大于 str2
如果返回值 = 0,则表示 str1 等于 str2
参数:str1 第一个字符串
str2 第二个字符串
函数原型:int strncmp(const char* str1, const char* str2);
返回值:如果返回值 < 0,则表示 str1 小于 str2
如果返回值 > 0,则表示 str1 大于 str2
如果返回值 = 0,则表示 str1 等于 str2
参数:str1 第一个字符串
str2 第二个字符串
n 要比较的字符个数
4 字符串追加函数:(注意:dest要大于dest+src,要不会造成内存溢出)
函数原型:char* strcat(char* dest, const char* src);
返回值:指向了dest的指针
参数:dest 目标内存
src 源串
函数原型:char* strncat(char* dest, const char* src, size_t n);
返回值:指向了dest的指针
参数:dest 目标内存
src 源串
n 要追加的字符个数
5 字符串置同一字符函数:(该函数可以将字符串的前n个字符都置为同一字符,我们经常使用到的置0操作便是使用该函数)
函数原型:void* memset(void* str, int c, size_t n)
返回值:指向了str的指针
参数:str 要操作的字符串
c 字符
n 要被设置为c的前多少个字符
六、字符串的长度问题总结(***重要问题***)
(1)不管程序员加不加'\0',系统都会为一个字符串添加'\0'
#include <stdio.h>
#include <string.h>
int main() {
printf("string' length is %ld (sizeof)\n", sizeof("hello"));
printf("string' length is %ld (sizeof with \\0)\n", sizeof("hello\0"));
return 0;
}
(2)字符串有两种长度:
无'\0'长度:strlen(字符串)
有'\0'长度:strlen(字符串) + 1 == sizeof(字符串字面值)
#include <stdio.h>
#include <string.h>
int main() {
printf("string' length is %ld (sizeof)\n", sizeof("hello"));
printf("string' length is %ld (strlen)\n", strlen("hello"));
return 0;
}
(3)不管是针对字符串常量还是字符数组,不能使用sizeof测定字符串长度!
sizeof是用来判断数据类型的,很多时候使用sizeof并不能准确的测定出字符串长度
见如下:
#include <stdio.h>
#include <string.h>
int main() {
char* str_con;
str_con = "hello";
printf("ues sizeof calculate str_con'length : %ld\n", sizeof(str_con));
// 输出为8 但是str_con的长度带'\0'是6,不带'\0'是5
char str_var[100];
memset(str_var, '\0', 100);
strcpy(str_var, " world!");
printf("ues sizeof calculate str_var'length : %ld\n", sizeof(str_var));
// 输出为100 但是str_var的长度带带'\0'是8,不带'\0'是7
return 0;
}