字符串与指针
- 字符串的存储方式
- 使用字符数组来存储
char str[5] = {'a','b','c','d','\0'};
char str1[] = {"jack"}; // 只有直接给字符串数组初始化为一个串的时候,才会自动追加\0,前提还要是字符数组长度足够
char str2[] = "jack";
- 使用字符指针来存储字符串数据
- 直接将一个字符串数据,初始化给一个字符指针
char* str = "jack";
printf("看看使用指针字符能否打印出来字符串:%s\n",str);
内存中的五大区域
-
内存中分为五块区域
-
栈
(是专门用来存储局部变量的,所有的局部变量都是声明在栈区域的)
-
堆
(它运行程序员手动的从堆 申请空间来使用,程序员自己申请指定字节数的空间就从堆中申请)
-
BSS 段
(是用来存储未初始化的全局变量和静态变量的,声明一个全局变量,如果我们没有初始化,在程序运行的最开始时候,这个全局变量是没有初始化的,运行起来以后才会给他初始化,这也需要个过程)
-
-
数据段/常量区
(用来存储已经初始化的全局变量和静态变量,还有常量数据,10,20 等)
-
代码段
(用来存储程序的代码/指令)
-
为什么要分为五个区域,每个与区是干嘛的?
- 不管是哪个区域都是用来存储数据的
- 不同的数据存储在不同的区域,方便系统的管理
存储字符串的两种方式的区别
- 使用字符数组来存储:将字符串数据的没一个字符存储到字符数组的元素中,追加一个"\0"表示结束
- char name[] = "jack";
- 使用字符指针来存储:直接为字符指针初始化一个字符串数据
- char* name = "jack";
- 1, 当他们都是局部变量的时候
// 局部变量
int main(){
char str[] = "jack"; // 字符数组
char* pstr = "jack"; // 字符指针
}
- 字符数组的方式:str 是字符数组,是一个变量,申请在栈区域内的,而它里面的字符串的没一个字符存储在这个字符数组的每一个元素中
-
字符指针的方式:pstr 是一个局部变量,这个指针变量是声明在栈区的,这个时候,字符串数据是存储在常量区的,字符串数据是以字符数组的形式存储在常量区
pstr 指针变量中存储的是"jack"这个字符串在常量区的地址
- 分别查看两种分时的内存地址
char str[] = "jack"; // 字符数组
char* pstr = "rose"; // 字符指针
printf("str[]的内存地址:%p\n",str);
printf("pstr 的内存地址:%p\n",pstr);
// 输出结果:可以看到这两个地址相差的很远,可以说明他们根本不在一个区域
str[]的内存地址:0x7ffeefbff44b
pstr 的内存地址:0x100000f17
- 当他们作为全局变量的时候
// 全局变量
char str[] = "jack"; // 字符数组
char* pstr = "jack"; // 字符指针
- str 字符数组是存储在常量区的,字符串的没一个字符时存储在这个数组中的每一个元素中
- pstr 指针也是存储在常量区的,字符串也是以字符数组的形式存储在常量区,pstr 指针中存储的是"jack"这个字符串在常量区中的地址
-
这两种方式的区别
-
- 在内存中存储的结构是不同的
-
以字符数组存储:无论如何是一个字符数组,然后字符串的每一个字符存储在数组的元素之中的
-
以字符串指针存储:无论如何首先有一个字符指针变量,字符串数据是以字符数组的形式存储在常量区的
-
- 可变与不可变
- 以字符数组存储的字符串数据,可以修改字符数组的元素
- 以字符数组的形式存储字符串数据,不管是全局还是局部的,都可以使用下标去修改字符数组中的任意一个字符
- 以字符指针的形式存储字符串数据,这个时候字符指针指向的字符串数据是无法修改的,不管是全局的还是局部的都不能修改字符串数据
-
字符串的恒定性
-
大前提:以字符指针形式存储的字符串
-
- 当我们以字符指针的形式存储字符串的时事,无论如何,字符串数据是存储在常量区的,并且一旦存储到常量区去,这个字符串数据就无法更改
-
- 当我们要以字符指针形式要将字符数据存储到常量区的时候,并不是直接将字符串存储到常量区,而是先检查常量区中国是否有相同的内容字符串,
-
如果有:才会将这个字符数组直接返回地址
-
如果没有,才会将这个字符串数据存储在常量区,再返回地址
// 字符串的恒定性
char* name1 = "jack";
char* name2 = "jack";
char* name3 = "jack";
printf("name1的内存地址:%p\n",name1);
printf("name2的内存地址:%p\n",name2);
printf("name3的内存地址:%p\n",name3);
// 打印的结果:
name1的内存地址:0x100000f02
name2的内存地址:0x100000f02
name3的内存地址:0x100000f02
-
- 当我们重新为字符指针初始化一个字符串的时候,挺不是修改原来的字符串,而是重新创建一个字符串,把这个新的字符串的地址赋值给它
-
- 最容易蒙圈的地方
// 最容易蒙圈的地方
char* name4 = "rose";
name4 = "新修改的值"; // 字符串可以重新进行赋值,直接将字符串赋值给字符指针就可以,这样就是在常量区新创建一个一个字符串,name4 字符指针直接指向了该新创建的字符串地址
char name5[] = "hhhhh";
// name5 = "wwwww"; // 这样写是不行的,name5 是一个字符数组,name5 代表是数组的第 0 个元素的地址,所以不能直接将字符串赋值给地址,只能通过下标修改.
- 建议使用字符指针来存储字符串数据
- 字符数组会规定字符串的长度,所以使用字符指针就可以避免这个问题
字符串数组
- 声明五个字符数组或者五个字符指针
- 使用一个二维的数组,来存储多个字符串,每一行就是一个字符串
char str[][10] = {
"jack",
"rose",
"shanshan",
"wahaha"
};
for (int i = 0; i < 4; i++) {
printf("数组的数据是:%s\n",str[i]);
}
- 缺点:每一个字符串的长度最多是列数-1
- 使用字符指针数组来存储才能多个字符串数据,每一个元素的类型是一个char*指针,
char* name[4] = {
"jack",
"rose",
"lili",
"xiaoru"
}; // 这是一个字符指针的一维数组,每一个数组的元素是一个字符指针,但是字符指针又不限定字符串的长度
for (int i = 0; i < 4; i++) {
printf("字符数组的每个元素的值是:%s\n",name[i]);
}
- 优点:每一个字符串的长度不限制
fput() 函数
- 作用:将字符串数据输出到指定的流中
- 流的分类
- 标准输出流-->控制台
- 文件流--> 磁盘上的文件
- 使用格式:
- fputs(要输出的字符串,指定的流);
- 要使用 fput()函数将字符串数据输出到标准输出流
// fputs()函数
// 输出到标准输出流,控制台
char* name = "jack";
fputs(name,stdout);// 要输出的字符串,输出的地方(stdout 标准输出流)
- 使用 fputs()将字符串写到文件中去
// 将字符串存储到文件中
// 1,要声明一个文件指针,这个文件指针指向磁盘上的文件
// fopen(文件的路径,操作文件的模式)
// "w",write 写入
// "r",读的模式
// "a",追加模式
// fouts(要写入的字符串,文件指针)
FILE* pfile = fopen("/Users/mac/Desktop/c语言学习/C语言学习库/指针/a.txt", "w");
char* name1 = "我要好好学习 oc语言";
fputs(name1, pfile);
// 当操作模式是"w",文件不存在就会创建一个,如果文件存在就会将源文件替换掉
// 当操作模式是"a",文件存在则追加在文件末尾,文件不存在就会创建这个文件
fgets()函数
- 作用:从制度的流中读取字符串,这个流可以是标准输入流,-->控制台,也可以是我们的文件流
- 使用 fgets 函数从标准输入流读取数组
- 使用 fgets 函数从控制台接收用户输入的字符串
- 对比:
- scanf 的缺点:不安全,输入的空格会被认为结束
- 的
- 语法:
- fgets 要将字符串存储到哪个数组中,最多接收多少个长度的字符串,指定流(stdin:代表标准输入流,也就是键盘流,控制台输入)
// fgets()函数从控制台读取
/*
char name[10]; // 定义一个数组
printf("请输入:\n");
fgets(name, 10, stdin); // 长度与数组长度一致的,超过会报错
// 为什么说是安全的
// 如果输入的输入的字符串长度大于等于了第二个参数,那么就会取 n-1 个,最后一个为\0,这样就不会崩溃了,所以说是安全的
printf("您输入的是:%s\n",name);
printf("----华丽的分割线----\n");
unsigned long len = strlen(name);
printf("len = %lu\n",len);
// for (int i = 0; i< len; i++) {
// if(name[i] == '\n'){
// name[i] = '\0';
// }
// }
// printf("您输入的是:%s\n",name);
// printf("现在的len = %lu\n",len);
// 这样循环不咋好,太浪费性能,我只需要知道最后一个值是不是\n,就行了,所以不用 for 循环
if(name[len-1] == '\n'){
name[len-1] = '\0';
};
printf("您输入的是:%s\n",name); // 这样将\n 替换成了\0,但是字符串的长度还是没有变的哦
printf("现在的len = %lu\n",len);
-
注意:
- 如果我们输入的字符串长度大于等于了第二个参数 n,只会接受前面的 n-1 个,然后最后一个自动是\0,这样就不会崩溃了
- 如果我们输入的字符串的长度刚好等于 n-1 那就是最完美的
- 如果我们输入的字符串的长度小于了n-1,那么就会将我们最后输入的换行字符"\n"一并接收,然后后面才是"\0"结束符,那么就需要我们自己将"\0"给剔除掉,
- 那么解决方案,输入完毕之后,判断字符数组当中存储的字符串最后一个是不是"\n",那么就将其替换为\0
-
使用 fgets 函数从文件流中读取数据,就是从磁盘上文件的内容
- 先创建一个文件指针
- 准备一个字符数组,准备存储读取到的字符串数据
- 使用 fgets 函数从指定的文件流中读取
// fgets 从文件h中读取
FILE* file = fopen("/Users/mac/Desktop/c语言学习/lianxi.txt", "r");
char fname[10];
fgets(fname, 10, file);
// char* fname;// 使用字符指针是不行的哦,指针是存储的地址
// fgets(fname, 10, file);
printf("从文件中读取的内容是:%s\n",fname);