C语言DAY12 - 指针

存储字符串的两种方式
  1. 使用字符数组来存储

    将字符串数据的每一个字符存储到数组中,追加一个’\0’代表结束
    char name[5] = {‘j’,’a’,’c’,’k’,’\0’};
    char name[] = {‘j’,’a’,’c’,’k’,’\0’};

    char name[] ={“jack”};
    直邮直接给字符串数组初始化一个串的时候,才会自动追加一个’\0’,前提是字符数组长度足够的情况下
    charname[] = “jack”;

  2. 使用字符指针来存储字符串数据.

    直接将一个字符串数据 初始化 给1个指针.
    char* name = “jack”;

内存中的五大区域

  1. 是专门用来存储局部变量的

  2. 允许程序员手动的从堆申请空间来使用.指定申请指定字节数的空间.
  3. BSS 段
    存储未初始化的全局变量和静态变量,如果我们没有初始化,在程序运行的最开始这个全局变量是没有初始化的
  4. 数据段 / 常量区
    用来存储已经初始化的全局变量和静态变量.还有常量数据.
  5. 代码段
    用来存储程序的代码/指令
    • 为什要分五个区域,来干嘛的
      不管是哪一个区域都是存储数据的
      不同的数据存储在不同的区域,方便系统的管理

存储字符串的两种方式的区别

  1. 使用字符数组来存储

    将字符串的每一个字符存储到字符数组的元素中,追加一个’\0’来表示结束

    char name[] = “jack”;

  2. 使用字符指针来存储

    直接为一个字符指针初始化一个字符串的数据

    char *name = “jack”;

当他们都是局部变量的时候
char name1[] = "jack";
char *name2 = "rose";
name1字符数组name2指针变量
字符数组在栈区指针变量在栈区
字符串存储在数组的每一个元素中字符串以字符数组的形式存储在常亮区
name1存储的地址是栈区中字符数组的地址name2存储的是常量区字符数组的地址
只创建了一个局部变量创建了一个局部变量,指向常量区中创建的全局变量
当他们存储在全局变量中的时候
name1字符数组name2指针变量
字符数组存储在常量区指针存储在常量区
字符串存储在这个数组中的每一个元素中字符串另外以字符数组的形式存储在常量区
只创建了一个全局变量创建了一个全局变量指针,指向一个全局变量字符数组
这两种方式的区别
name1字符数组name2字符指针
是一个字符数组是一个字符指针变量
字符串就存在自己本身这个数组内字符串是以字符数组的形式存储在常量区中的
不管是全局还是局部,都可以修改自己的字符数组中的每一个元素不管是全局还是局部,都不能通过指针去修改那一个字符数组
可变不可变

字符串的恒定性

大前提: 是以字符指针形式存储的字符串.
  1. 当我们以字符指针的形式存储字符串的时候

    字符串数据是存储在常量区的.
    并且 一旦存储到常量区中去. 这个字符串数据就无法更改.

  2. 当我们以字符指针的形式要将字符串数据存储到常量区的时候
    先检查常量区中是否有相同内容的字符串.
    如果有,直接将这个字符串的地址拿过来返回.
    如果没有,才会将这个字符串数据存储在常量区.
  3. 当我们重新为字符指针初始化1个字符串的时候

    并不是修改原来的字符串. 而是重新的创建了1个字符串.
    把这个新的字符串的地址赋值给它.

  4. 最容易蒙圈的地方.

    char *name = “jack”;
    name = “rose”;
    这样是可以的.但是
    不是把”jack”改成了”rose” 而是重新创建了1个”rose”
    把”rose”地址赋值给name

    char 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);
  • 以字符指针存储字符串数据 和 字符数组 存储字符串数据的优势.

    建议大家使用字符指针来存储字符串数据:

    1. 以字符数组来存储字符串数据: 长度固定.一旦创建以后,就最多只能存储这么多个长度的字符串数据了.
      char name[] = “jack”;
      虽然不指定长度,但是已经固定成了5个长度,想在添加就不可能了

    2. 以字符指针的形式存储字符串数据.长度任意
      char *name1 = “jack”;
      name = “fanyizeng”;


字符串数组
声明二维字符数组,存储多个字符串
  1. 声明5个字符数组或者5个字符指针.

    char name1[] = “jack”;
    char *name1 = “jack”;
    //这两种都可以,但是不便于管理

  2. 使用一个二维的字符数组 来存储多个字符串. 每1行就是1个字符串.

    char names[][10] =
    {
    “jack”,”rose”,”lily”
    };
    //这个数组的每一行是 1个长度为10的char一维数组.最多存储长度为9的字符串.
    缺点: 每1个字符串的长度不能超过 列数-1

  3. 使用字符指针数组来存储多个字符串数据.

    char* names[4];
    char* names[];
    //这是1个一维数组.每1个元素的类型是char指针.
    char* names[ ] = {“jack”,”rose”,”lily”,”lilei”};

    1. names数组的元素的类型是char指针
    2. 初始化给元素的字符串数据是存储在常量区的.
    3. 元素中存储的是 字符串在常量区的地址.
    4. 优点: 每1个字符串的长度不限制.
字符串数组的排序
  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;
        }
    
    }
    

    }

  2. 选择排序,按照字母长度

    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]);
    }

  3. 打印结果

    for(int i = 0 ;i < len;i++)
    {
    printf(“%s\n”,countries[i]);
    }

fputs( ); f –> File
将字符串数据 输出到 指定的流中.
  • 流:

    1. 标准输出流->控制台.
    2. 文件流 –> 磁盘上的文件.

    使用格式:
    fputs(要输出的字符串,指定的流);

    1. 要使用fputs函数将字符串数据输出到标准输出流.

    char *name = “我爱黑马”;
    fputs(name, stdout);

    1. 将字符串存储到文件中.

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( )函数.
从指定的流中读取字符串.
  1. 这个流可以是我们的标准输入流–>控制台.
  2. 也可以是我们的文件流.

    • 使用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 = &num;
无法通过 *p1指针修改指针指向的变量的值
num = 100 是可以的
指针是只读指针
int age = 20;
p1 = &age;
p1指针变量的值是可以修改的
int const * p1 = &num;
效果同上
但是!!:
int* const p1 = & num;
p1的值不能修改,但是可以通过p1去修改指向的变量的值
*p1 = 20; 是可以的

int const * const p1 = &num
都不能修改!
const 的使用场景
被 const 修饰的变量是只读变量,只能取值,不能改值
  1. 当某些数据是固定的,在整个程序运行期间都不会发生也不允许变化,就用 const
  2. 函数的参数是一个指针的时候,这个时候函数的内部有可能修改实参变量的值.加一个 const 让调用者不用担忧.

    朋友之间的信任,放心给我,我不改
    void printArray(const int arr[],int len)

malloc 函数

如何向 堆区 申请字节空间
1. 我们在堆中申请的字节空间,如果我们不主动释放,那么系统是不会释放的,除非程序结束.
2. 在堆中申请字节空间的步骤

a.申请
b.使用
c.释放

3. 如何申请?

#include<stdlib.h>
malloc()
calloc()
realloc()

- malloc 函数

  1. 作用:向堆空间申请指定字节的空间来使用

    参数直邮一个:size_t 类型,也就是 unsigned long
    2.申请

    malloc(4)
    向堆内存申请连续的4个字节空间

  2. 返回值

    void *代表没有类型的指针.
    返回的是创建的空间的第一个字节的地址
    4.怎么接收

    void *p1 = malloc(4);
    但是没有意义,操作没有类型的指针会报错
    所以看你想怎么操作申请的字节空间
    1个个操作
    char *ch = malloc(4);
    *ch = 100;
    就把100赋给了第一个字节
    4个字节,当做整形的
    int *num = malloc(4);
    *p1 = 100
    就把100给了4个字节
    操作的时候以4个字节为基本单位

  3. 操作

    int* p1 = malloc(24);
    *p1 = 10
    *(p1+1) = 20;
    *(p1+2) = 30;
    *(p1+3) = 40;
    *(p1+4) = 50
    *(p1+5) = 60

注意
  1. 在堆区申请的空间是从低到高申请的.
  2. 每次申请的字节地址都是从0开始(最后一位)
  3. 每次申请不一定挨在一起
  4. 但是申请的每一组字节还是跟以前一样连贯的
  5. 在堆区申请的字节里面是垃圾值,不会自动清零
  6. 在申请字节空间的时候可能会申请失败,失败后返回的指针就是 NULL 值.
  7. 最好判断一下是否申请成功

    if(p1)//NULL值为0
    {
    //代表申请成功,要做的事情放在这里.
    }

    • 释放
free(p1);
//如果没有 free 程序结束才会释放.
程序运行的越久,不释放就越卡
calloc 函数
跟 malloc 一样申请指定字节数的空间
  • 语法

    int* p1 = calloc(4,sizeof(int))
    参数1:申请多少个单位的空间
    参数2:每个单位占长度

优势:
1. 申请完自动清零,初始化

realloc 函数
扩容
  1. 我们有了指针,几乎就可以操作上内存中任意一个字节
  2. 但是这样可能会出现一些问题,建议不要乱来最好只操作我们申请的字节空间
  3. 当我们在堆申请的字节空间不够用的时候,就可以使用 realloc 函数来扩容

    int* p2 = realloc(p1,4);
    第一个参数,之前的指针名
    第二个参数,要扩容到几个单位
    p2 扩容后搬家的新家的地址
    如果原来的空间后面够用,就直接在屁股后面扩容
    如果剩下的空间不够扩容,就重新找一块足够的空间,将以前的数据拷贝过来,并且释放之前的空间

calloc 创建数组

int* arr = calloc(10,sizeof(int));
数组创建在堆中,长度是10
int* arr1 = realloc(arr,15);
扩容后用新的数组名接收
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值