1、字符串的基本概念
(1)字符串的存储
字符串是存放在字符数组中的。比如字符串“hello”,在内存中是这么存放的:
这里要注意,在字符串的最后会加上一个'\0',也被称为NUL字符,表示字符串的结束位。比如定义char str[] = "hello",则sizeof(str)的长度是6,不是5。
(2)字符串的访问
既然字符串是字符按数组的方式存储的,我们可以用访问一维数组的方式来访问字符串。
char str[] = "hello";
printf(" %c\n", str[1]);
char* ch = &str[1];
printf(" %c\n", *ch);
(3)字符串常量池
用char数组str[]的方式定义字符串,我们也可以用下面的方式定义一个字符串:
char *str = "hello";
这里的str其实是一个char*型的指针变量,它指向字符串"hello"的首地址。
这种方式定义字符串时,"hello"是字符串常量,这个常量就放在字符串常量池中。
我们可以用代码来验证下:
const char* pstr1 = "hello";
const char* pstr2 = "hello";
printf(" p1 = %p, p2 = %p\n", pstr1, pstr2);
运行后发现pstr1, pstr2这2个地址是相同的,且每次运行地址都一样。这说明这个字符串常量是编译时就确定好的。
(4)sizeof()和strlen()
sizeof是C语言中的一个单目运算符,用来计算数据类型所占空间的大小,单位为字节;而strlen是一个函数,用来计算字符串长度(不包括字符串结束位NUL)。
char str1[] = "hello";
char str2[10] = "hello";
printf(" sizeof(str1) = %d, strlen(str1) = %d\n", sizeof(str1), strlen(str1));
printf(" sizeof(str2) = %d, strlen(str2) = %d\n", sizeof(str2), strlen(str2));
2、定义字符串的几种方式
(1)char str[] = {'h', 'e', 'l', 'l', 'o', '\0'};
定义了一个字符数组,长度为6,数组里的元素分布如下:{'h', 'e', 'l', 'l', 'o', '\0'}。
假如末尾没有NUL字符:char str[] = {'h', 'e', 'l', 'l', 'o' };
printf()输出str的值会是什么呢?sizeof()和strlen()的值分别是什么呢?
char str1[] = { 'h', 'e', 'l', 'l', 'o' };
printf(" %s\n", str1);
printf(" sizeof(str1) = %d, strlen(str1) = %d\n", sizeof(str1), strlen(str1));
这时,输出的str1是hello+一些乱码;sizeof(str1)是5,而strlen(str1)的值是个变数,每次的输出不一定相同。
因为字符串是以NUL作为字符串结束位的,现在缺少这个结束位,程序就没有办法做出正确的处理了。
(2)char str[] = "hello";
定义了一个字符数组,长度为6,系统会在末尾自动加上字位结束符NUL。
(3)char *pstr = "hello";
定义了一个字符指针变量,指向常量池中的字符常量"hello"。
4、字符指针变量和字符数组的比较
char *str1定义的是一个字符指针变量;char str2[10]定义的是一个字符数组,数组名代表数组元素首地址。
这是两个不同的概念,主要有以下区别:
(1)字符指针变量存放的是地址,指向字符串的第一个元素;字符数组就是一个存放字符的数组,一个元素就是一个字符。
(2)字符指针变量的值是可以改变的,但字符数组名的值不能改变(它只代表数组的首地址)。
const char *str1 = "hello";
printf(" %c\n", *str1);
str1++; //可以更改
printf(" %c\n", *str1);
char str2[10] = "hello";
str2++; //报错
(3)字符指针指向的字符串的内容不能被更改,但字符数组中各元素的值可以被更改。
const char *str1 = "hello";
str1[2] = 'a'; //报错
char str2[10] = "hello";
str2[2] = 'a'; //可以
(4)我们可以用str2[1], *(str2 + 1)这样的方式访问数组元素;对于字符指针变量,同样也可以用str1[1], *(str1 + 1)这样的方式访问。
const char *str1 = "hello";
printf(" %c, %c \n", str1[1], *(str1 + 1));
char str2[10] = "hello";
printf(" %c, %c \n", str2[1], *(str2 + 1));
(5)初始化的含义
char *str1 = "hello";
等价于:
char *str1;
str1 = "hello"; //把字符串的首地址给str
但字符数组的初始化:
char str2[10] = "hello";
不等价于:
char str2[10];
str2 = "hello"; //报错
其实这里的str1是个char *类型的指针变量,即字符指针变量,把字符数组的首地址赋给这个指针变量是没问题的。
而str2是数组的名字,代表字符数组首元素的地址,我们可以给这个地址赋值,但不能让地址去指向另外一个地址。
char str1[10];
*str1 = 'h'; //可以给地址赋值
strcpy(str1, "hello"); //可以直接内存拷贝
5、字符指针作函数参数
前面我们知道可以用两种方式来定义字符串,一种是字符数组,另一种是字符串指针变量。当函数的形参是字符串时,我们可以传字符数组的首地址,也可以传字符串指针变量。
(1)传字符数组的首地址
void print_str(char str[]) {
int i = 0;
while (str[i] != '\0') {
printf("%c", str[i]);
i++;
}
}
int main()
{
const char *str1 = "hello";
print_str((char*)str1);
char str2[10] = "hello";
print_str(str2);
return 0;
}
(2)传字符串指针变量
void print_str(char* str) {
int i = 0;
while (str[i] != '\0') {
printf("%c", str[i]);
i++;
}
}