存储字符串的两种方式
使用字符数组来存储
将字符串数据的每一个字符存储到数组中,追加一个’\0’代表结束
char name[5] = {‘j’,’a’,’c’,’k’,’\0’};
char name[] = {‘j’,’a’,’c’,’k’,’\0’};char name[] ={“jack”};
直邮直接给字符串数组初始化一个串的时候,才会自动追加一个’\0’,前提是字符数组长度足够的情况下
charname[] = “jack”;使用字符指针来存储字符串数据.
直接将一个字符串数据 初始化 给1个指针.
char* name = “jack”;
内存中的五大区域
- 栈
是专门用来存储局部变量的
- 堆
允许程序员手动的从堆申请空间来使用.指定申请指定字节数的空间. - BSS 段
存储未初始化的全局变量和静态变量,如果我们没有初始化,在程序运行的最开始这个全局变量是没有初始化的 - 数据段 / 常量区
用来存储已经初始化的全局变量和静态变量.还有常量数据. - 代码段
用来存储程序的代码/指令
- 为什要分五个区域,来干嘛的
不管是哪一个区域都是存储数据的
不同的数据存储在不同的区域,方便系统的管理
- 为什要分五个区域,来干嘛的
存储字符串的两种方式的区别
使用字符数组来存储
将字符串的每一个字符存储到字符数组的元素中,追加一个’\0’来表示结束
char name[] = “jack”;
使用字符指针来存储
直接为一个字符指针初始化一个字符串的数据
char *name = “jack”;
当他们都是局部变量的时候
char name1[] = "jack";
char *name2 = "rose";
name1字符数组 | name2指针变量 |
---|---|
字符数组在栈区 | 指针变量在栈区 |
字符串存储在数组的每一个元素中 | 字符串以字符数组的形式存储在常亮区 |
name1存储的地址是栈区中字符数组的地址 | name2存储的是常量区字符数组的地址 |
只创建了一个局部变量 | 创建了一个局部变量,指向常量区中创建的全局变量 |
当他们存储在全局变量中的时候
name1字符数组 | name2指针变量 |
---|---|
字符数组存储在常量区 | 指针存储在常量区 |
字符串存储在这个数组中的每一个元素中 | 字符串另外以字符数组的形式存储在常量区 |
只创建了一个全局变量 | 创建了一个全局变量指针,指向一个全局变量字符数组 |
这两种方式的区别
name1字符数组 | name2字符指针 |
---|---|
是一个字符数组 | 是一个字符指针变量 |
字符串就存在自己本身这个数组内 | 字符串是以字符数组的形式存储在常量区中的 |
不管是全局还是局部,都可以修改自己的字符数组中的每一个元素 | 不管是全局还是局部,都不能通过指针去修改那一个字符数组 |
可变 | 不可变 |
字符串的恒定性
大前提: 是以字符指针形式存储的字符串.
- 当我们以字符指针的形式存储字符串的时候
字符串数据是存储在常量区的.
并且 一旦存储到常量区中去. 这个字符串数据就无法更改. - 当我们以字符指针的形式要将字符串数据存储到常量区的时候
先检查常量区中是否有相同内容的字符串.
如果有,直接将这个字符串的地址拿过来返回.
如果没有,才会将这个字符串数据存储在常量区. 当我们重新为字符指针初始化1个字符串的时候
并不是修改原来的字符串. 而是重新的创建了1个字符串.
把这个新的字符串的地址赋值给它.最容易蒙圈的地方.
char *name = “jack”;
name = “rose”;
这样是可以的.但是
不是把”jack”改成了”rose” 而是重新创建了1个”rose”
把”rose”地址赋值给namechar name[] = “jack”;
name = “rose”;
这样是不行的. name是数组名 代表数组的地址. 不能为数组名赋值.name[0] = 'r'; name[1] = 'o'; name[2] = 's'; name[3] = 'e'; name[4] = '\0' 这么做是可以的 直接修改数组的元素
如果 char name[] = “ja”;
就不能给 name[3-4]赋值,因为空间不够.
面试题
有1个字符串
"dhiwdhiwdniwneiwneriwneiwneiwnewnerwneriwnrenwieniwneiwneiwneiwneiwnewi";
求这个字符串中 字符'e' 出现的次数.
思路:
声明1个整型的变量记数:
遍历每1个字符.判断这个字符如果是'e' 计数器++
char *str = "edhiwdhiwdniwneiwneriwneiwneiwnewnerwneriwnrenwieniwneiwneiwneiwneiwnewi";
int count = 0;//计数器.
//遍历字符串中的每1个字符.
//通过指针可以找到字符串的第1个字符.
//那么我们就可以不断+1 找下1个字符. 直到遇到'\0'结束.
int i = 0;
while (str[i] != '\0') //中括弧的本质. str[i] ==== *(str+i)
{
if(str[i] == 'e')
{
count++;
}
i++;
}
printf("count = %d\n",count);
以字符指针存储字符串数据 和 字符数组 存储字符串数据的优势.
建议大家使用字符指针来存储字符串数据:
以字符数组来存储字符串数据: 长度固定.一旦创建以后,就最多只能存储这么多个长度的字符串数据了.
char name[] = “jack”;
虽然不指定长度,但是已经固定成了5个长度,想在添加就不可能了以字符指针的形式存储字符串数据.长度任意
char *name1 = “jack”;
name = “fanyizeng”;
字符串数组
声明二维字符数组,存储多个字符串
声明5个字符数组或者5个字符指针.
char name1[] = “jack”;
char *name1 = “jack”;
//这两种都可以,但是不便于管理使用一个二维的字符数组 来存储多个字符串. 每1行就是1个字符串.
char names[][10] =
{
“jack”,”rose”,”lily”
};
//这个数组的每一行是 1个长度为10的char一维数组.最多存储长度为9的字符串.
缺点: 每1个字符串的长度不能超过 列数-1使用字符指针数组来存储多个字符串数据.
char* names[4];
char* names[];
//这是1个一维数组.每1个元素的类型是char指针.
char* names[ ] = {“jack”,”rose”,”lily”,”lilei”};- names数组的元素的类型是char指针
- 初始化给元素的字符串数据是存储在常量区的.
- 元素中存储的是 字符串在常量区的地址.
- 优点: 每1个字符串的长度不限制.
字符串数组的排序
冒泡排序,按照字母顺序
//1. 先计算这个数组的长度.
int len = sizeof(countries) / sizeof(countries[0]);//2.使用冒泡排序
for(int i = 0; i < len - 1; i++)
{
for(int j = 0; j < len - 1 - i; j++)
{int res = strcmp(countries[j],countries[j+1]); if(res > 0) //说明j 比j+1小. { char* temp = countries[j]; countries[j] = countries[j+1]; countries[j+1] = temp; } }
}
选择排序,按照字母长度
int len = sizeof(countries) / sizeof(countries[0]);
for(int i = 0; i < len - 1; i++)
{
for(int j = i + 1; j < len; j++)
{
unsigned long len1 = strlen(countries[i]);
unsigned long len2 = strlen(countries[j]);
if(len1 < len2)
{
char* temp = countries[i];
countries[i] = countries[j];
countries[j] = temp;
}
}
}for(int i = 0; i < len;i++)
{
printf(“%s\n”,countries[i]);
}打印结果
for(int i = 0 ;i < len;i++)
{
printf(“%s\n”,countries[i]);
}
fputs( ); f –> File
将字符串数据 输出到 指定的流中.
流:
- 标准输出流->控制台.
- 文件流 –> 磁盘上的文件.
使用格式:
fputs(要输出的字符串,指定的流);- 要使用fputs函数将字符串数据输出到标准输出流.
char *name = “我爱黑马”;
fputs(name, stdout);- 将字符串存储到文件中.
a. 要先声明1个文件指针.指向磁盘上的文件.
FILE* pFile(指针名) = fopen("/Users/Itcast/Desktop/abc.txt"(路径), "w"(模式));
1. fopen函数可以创建1个指向文件的指针.
2. fopen函数的两个参数:
第1个参数: 文件的路径,代表创建的指针就指向这个文件.
第2个参数: 操作文件的模式. 你要对这个文件做什么操作.必须要告诉他.
1. "w" --> write 代表你要向这个文件写入内容.
2. "r" --> read 代表你从这个文件中读取数据.
3. "a" --> apped 追加 代表你向这个文件中追加数据
b. 使用fputs函数将字符串写入到指定的文件流中.
fputs(字符串,文件指针);
fputs(name, pFile);
c. 写完之后,一定要记得使用fclose()函数 将这个文件关闭.
fclose(pFile);
fgets( )函数.
从指定的流中读取字符串.
- 这个流可以是我们的标准输入流–>控制台.
也可以是我们的文件流.
- 使用fgets函数从标准输入流中读取数据.
前面学习的scanf函数gets函数也可以实现这个功能.
scanf的缺点 a. 不安全. b. 输入的空格会被认为结束. gets函数的缺点. a. 不安全. fgets函数的优点 a. 安全 b. 空格也会一并接收.
使用fgets函数从控制台接收用户输入字符串.
char input[5];
fgets(input,n,stdin);
第2个参数: 我们写1个n 那么函数最多就接收n-1个长度的字符串.
这个参数一般情况下和第1个参数数组的长度一致.
stdin: 代表标准输入流. 也就是键盘流 控制台输入.
如果我们输入的字符串的长度小于了n-1
解决方案: 输入完毕之后,判断字符数组中存储的字符串最后1个是不是'\n'
如果是'\n' 那么就将其替换为'\0'.
size_t len = strlen(input);
if(input[len - 1] == '\n')
{
input[len-1] = '\0';
}
- 使用fgets函数从文件流中读取数据:
a. 先创建1个文件指针.
//1. 创建1个读取文件的文件流.
FILE* pFile = fopen("/Users/Itcast/Desktop/abc.txt", "r");
b. 准备1个字符数组.准备存储读取到的字符串数据.
char content[50];
c. 使用fgets函数从指定的文件流中读取.
fgets(content, 50, pFile);
d. 读取成功:
printf("读取的内容是: %s\n",content);
e. 关闭文件流
fclose(pFile);
const关键字
const 是1个关键字,是来修饰我们的变量的.
const int num = 10;
一般情况,const 修饰的变量具备一定程度上的不可变性
const 修饰基本数据类型的变量
基本数据类型:int , double , float , char
1. const int num =10;
这个时候,num 的值只能取值,不能修改,叫做只读变量
int const num = 10;
写前面写后面一样
数组
const int arr[4] = {10,20,30,40};
数组的元素的值不能修改
int const arr[4] = {10,20,30,40};
写前面后面都一样
指针
int num = 10;
const int* p1 = #
无法通过 *p1指针修改指针指向的变量的值
num = 100 是可以的
指针是只读指针
int age = 20;
p1 = &age;
p1指针变量的值是可以修改的
int const * p1 = #
效果同上
但是!!:
int* const p1 = & num;
p1的值不能修改,但是可以通过p1去修改指向的变量的值
*p1 = 20; 是可以的
int const * const p1 = &num
都不能修改!
const 的使用场景
被 const 修饰的变量是只读变量,只能取值,不能改值
- 当某些数据是固定的,在整个程序运行期间都不会发生也不允许变化,就用 const
函数的参数是一个指针的时候,这个时候函数的内部有可能修改实参变量的值.加一个 const 让调用者不用担忧.
朋友之间的信任,放心给我,我不改
void printArray(const int arr[],int len)
malloc 函数
如何向 堆区 申请字节空间
1. 我们在堆中申请的字节空间,如果我们不主动释放,那么系统是不会释放的,除非程序结束.
2. 在堆中申请字节空间的步骤
a.申请
b.使用
c.释放
3. 如何申请?
#include<stdlib.h>
malloc()
calloc()
realloc()
- malloc 函数
作用:向堆空间申请指定字节的空间来使用
参数直邮一个:size_t 类型,也就是 unsigned long
2.申请malloc(4)
向堆内存申请连续的4个字节空间返回值
void *代表没有类型的指针.
返回的是创建的空间的第一个字节的地址
4.怎么接收void *p1 = malloc(4);
但是没有意义,操作没有类型的指针会报错
所以看你想怎么操作申请的字节空间
1个个操作
char *ch = malloc(4);
*ch = 100;
就把100赋给了第一个字节
4个字节,当做整形的
int *num = malloc(4);
*p1 = 100
就把100给了4个字节
操作的时候以4个字节为基本单位操作
int* p1 = malloc(24);
*p1 = 10
*(p1+1) = 20;
*(p1+2) = 30;
*(p1+3) = 40;
*(p1+4) = 50
*(p1+5) = 60
注意
- 在堆区申请的空间是从低到高申请的.
- 每次申请的字节地址都是从0开始(最后一位)
- 每次申请不一定挨在一起
- 但是申请的每一组字节还是跟以前一样连贯的
- 在堆区申请的字节里面是垃圾值,不会自动清零
- 在申请字节空间的时候可能会申请失败,失败后返回的指针就是 NULL 值.
最好判断一下是否申请成功
if(p1)//NULL值为0
{
//代表申请成功,要做的事情放在这里.
}
- 释放
free(p1);
//如果没有 free 程序结束才会释放.
程序运行的越久,不释放就越卡
calloc 函数
跟 malloc 一样申请指定字节数的空间
语法
int* p1 = calloc(4,sizeof(int))
参数1:申请多少个单位的空间
参数2:每个单位占长度
优势:
1. 申请完自动清零,初始化
realloc 函数
扩容
- 我们有了指针,几乎就可以操作上内存中任意一个字节
- 但是这样可能会出现一些问题,建议不要乱来最好只操作我们申请的字节空间
当我们在堆申请的字节空间不够用的时候,就可以使用 realloc 函数来扩容
int* p2 = realloc(p1,4);
第一个参数,之前的指针名
第二个参数,要扩容到几个单位
p2 扩容后搬家的新家的地址
如果原来的空间后面够用,就直接在屁股后面扩容
如果剩下的空间不够扩容,就重新找一块足够的空间,将以前的数据拷贝过来,并且释放之前的空间
calloc 创建数组
int* arr = calloc(10,sizeof(int));
数组创建在堆中,长度是10
int* arr1 = realloc(arr,15);
扩容后用新的数组名接收