c语言实战开发(别浪费空间)2019-12-07 Day12

内容

1:系统的四个内存空间
2:动态分配内存
3:demo:输入输出名字
4:文件操作
5:结构体

一.系统的四个内存空间

字符常量区:

特点
①:只能读取,不能修改(只读)
②:空间也是由系统申请释放
③:生命周期中,字符串常量与系统共存亡,而数值常量,比如12,31,'c',等是 立即数存储 放常量,一般不占用额外的存储空间,即拿来主义。比如int a = 12,那12直接就赋给a了,不给12留额外的存储空间
④全局const变量存储在常量区,局部const不存在常量区,而是存在栈区。

静态全局区:全局变量和static变量。

特点:
1:存在静态区/静态全局区/静态存储区
2:会被自动初始化为0
3:生命周期与系统共存亡
4:运行时,在加载资源阶段分配空间。而栈区是相当于执行到int a 才开始分配空间
(1)全局变量/外部变量
特点:
①:初始化必须用常量,而不能用abc等变量
②:作用域是所有文件都可见
③:存储类说明符是extern
④:局部位置也能声明全局变量,那这样就不能省略extern
(2)静态全局变量(加extern变成外部的,加static变成静态的)
特点:与extern不同,static只在所在文件下有效,别的文件用不了
(3)静态局部变量/局部静态变量
特点:
①:生命周期是与程序共存亡,相当于把局部变量的生命周期延长了
②:作用域与局部变量相同
代码示例:
{int a = 1;
static int b = 1;
a++;b++;}
引用三次这个函数,发现每次打印都是a= 2,b = 2/3/4.充分体现了局部变量与静态局部变量的唯一区别:生命周期不同。

栈区:

局部变量。默认1M空间,由系统自动申请自动释放(离开作用域就自动收回)(一般是在花括号内)。默认有auto修饰。

堆区:

自己申请的内存空间。 malloc calloc realloc。必须自己释放,不释放的话也是跟程序共存亡

举例

#include
//常量字符串的内存空间由系统自动分配
//在常量区里面分配 当程序结束才会被释放 
char* test(){
    char *name = "jack";//变量本身name被释放掉,(这里name是指针而不是字符串名字)但是jack没有释放。也就是说这里的jack是在常量区分配的。
    return name;//注意这里返回name 
}
int main (){
    char *p; 
    p = test();
    printf("%s",p);//注意这里是%s 
    return 0;
}

再次强调:变量本身name被释放掉,(这里name是指针而不是字符串名字)但是jack没有释放。也就是说这里的jack是在常量区分配的。

#include
char *test1(){
    char name[10] = "jack";//和前一个不同的是,这里过了这个函数之后,里面的内存全部都释放掉了,也就是name里面的所有东西。所以打印出乱码 。也就是说这里的jack是在栈区里面分配的
    return name;
}
int main (){
    char *p; 
    p = test1();
    printf("%s",p);//注意这里是%s 
    return 0;
}

前一个不同的是,这里过了这个函数之后,里面的内存全部都释放掉了,也就是name里面的所有东西。所以打印出乱码 。也就是说这里的jack是在栈区里面分配的

#include
int count = 0;//当成全局变量,相对于当前文件来说的 
void test3(){
    count++;
    printf("count = %d",count);
}
int main (){
    test3();
    test3();
    return 0;
}
#include

void test3(){
    static int count = 0;//静态变量,只会被定义一次,生命周期是从程序一开始到结束,(不是开始定义到结束) 。 这里的全局变量就是相对于整个工程来说的。当然静态变量在c语言中用的很少,而在java中用的较多。
    count++;
    printf("count = %d\n",count);
}
int main (){
    test3();
    test3();
    return 0;
}

二.动态分配内存

为什么需要:①:存储的数据需要延长生命周期(也就是让他被释放地晚一点)
②:一个指针变量需要存储数据,但是变量本身只能存地址,不能存数据。需要分配内存空间来存储数据。

#include
#include


int main (){
    //从终端接收字符串
    //char name[100] = {};//看这个地址有没有内存空间存这个字符串,在这里是数组,所以有内存,所以可以运行。 
    char *name ;//把上面一行注释掉,然后用这一行,这里只是声明了一个变量,里面没有存任何地址,也就是说系统根本没有给它分配任何内存空间,所以不会指向任何空间,写不进去东西。
    //如果使用指针变量接收数据,必须先为这个指针变量分配一片指向的内存空间。也就是说先有内存,才能存值。
    //使用 malloc (memory alloc 的结合),使用之前必须导入一个头文件,stdlib.h 
    name = (char *)malloc(10*sizeof(char));//这样就分配了十个字节内存空间 
    if(name == NULL){//申请内存失败,这一步经常忘记
    exit(EXIT_FAILURE); 
    }
    scanf("%s",name);
    printf("%s\n",name); 
       //使用完毕必须自己手动释放内存
        free(name);//也就是说malloc和free是配套使用的,这里也经常忘记
    return 0;
}

动态改变已经分配的内存的大小用realloc ,也就是说要增加或减少内存(在已有的基础之上)


    name = (char *)realloc(name,20*sizeof(char)); 
        if(name == NULL){
                //退出之前必须将之前分配的内存释放掉。
        free(name);
        exit(EXIT_FAILURE); 
    }

三.demo:输入输出名字

从终端输入人的名字,询问是否继续,然后输出姓名。要求不能浪费内存空间

#include 
#include 
#include 

//从终端接收字符串 返回这个字符串的首地址
char *inputName(){
    //1.定义一个指针变量 指向字符串的首地址
    char *pName = NULL;
    //2.接收输入
    int i = 0;
    //3.提示操作
    printf("请输入人名:");
    while (1) {
        //接收一个字符
        char c = getchar();
        //判断这个字符是不是\n
        if (c == '\n') {
            //输入结束
            break;
        }
        //判断是不是第一个字符
        if(i == 0){
            //使用malloc分配内存
            pName = (char *)malloc(1*sizeof(char));
            //判断是否分配成功
            if(pName == NULL){
                exit(EXIT_FAILURE);
            }
            pName[0] = c;
        }else{
            //使用realloc在之前的基础上加一个
            pName = (char*)realloc(pName, (i+1)*sizeof(char));
            //判断是否分配成功
            if(pName == NULL){
                exit(EXIT_FAILURE);
            }
            pName[i] = c;
        }
        
        i++;
    }
    //将当前的字符串首地址返回
    return pName;
}

//是否继续
bool isContinue(){
    printf("是否继续(y/n)?:");
    while (1) {
        char c = getchar();
        getchar();
        if (c == 'y'){
            return true;
        }else if(c == 'n'){
            return false;
        }else{
            printf("输入格式不对,请重新输入:");
        }
    }
}
//初始化整个数组
char **initNames(int *pNum){
    //1.定义指针变量指向每个名字的首地址的内存
    char **pHead = NULL;
    
    //2.记录元素个数
    int i = 0;
    while (1) {
        //判断是不是第一个
        //第一个使用malloc分配内存
        if (i == 0) {
            pHead = (char **)malloc(1*sizeof(char *));
            if (pHead == NULL) {
                exit(EXIT_FAILURE);
            }
            
            //输入人名 将地址放到pHead对应位置
            pHead[0] = inputName();
        }else{
            //使用realloc重新再增加一个元素
            pHead = (char **)realloc(pHead, (i+1)*sizeof(char *));
            if (pHead == NULL) {
                exit(EXIT_FAILURE);
            }
            //输入人名 将地址放到pHead对应位置
            pHead[i] = inputName();
        }
        
        i++;
        
        //是否继续
        bool result = isContinue();
        if (result == false) {
            break;
        }
    }
    
    *pNum = i;
    return pHead;
}

void show(char **pHead, int num){
    printf("输入%d个名字:\n",num);
    for (int i = 0; i < num; i++) {
        printf("%s\n",pHead[i]);//相当于转换了类型 
    }
    printf("\n");
}

int main(int argc, const char * argv[]) {
    char **pHead = NULL;
    int count = 0;
    pHead = initNames(&count);
    show(pHead, count);
    return 0;
}

上面的没有释放,所以还不完善
下面是自己写的

#include
#include
bool isContinue(){
    while(1){
    printf("是否继续(y/n):");
    char c = getchar();
    getchar();//拿走回车
    if(c == 'y'){//注意要加单引号 
        return true;
    } 
    if(c == 'n'){
        return false;
    }else{
        printf("输入格式有误,请重新输入");
        //这里就怕输入多个字符,这样就会打印出多个上面那一行 输入格式有误。 
    }
    }
    
}
char* yiji(){
    char* zifuchuan = NULL;
    int i = 0;//记录到了第几个字符
    printf("请输入名字:");
    while(1){
        char c = getchar();
        if(c == '\n'){
        break;
        }
        if(i == 0){
        zifuchuan = (char*)malloc(1*sizeof(char));
        if(zifuchuan == NULL){
            exit(EXIT_FAILURE);
        }
        zifuchuan[i] = c;
        } else{
        zifuchuan = (char*)realloc(zifuchuan,(1+i)*sizeof(char));//realloc的格式一开始写错了,忘了写realloc 
        if(zifuchuan == NULL){
            exit(EXIT_FAILURE);
        }
        zifuchuan[i] = c;//这一行一开始忘了写 
        }
        i++;
        
    }
    return zifuchuan;
}
char** shuru (int *p){//这里返回值是char**因为要返回pHead 
    char **pHead = NULL;//重定义了,没关系 
    int i = 0;//记录第几个名字
    while(1){
        
    if(i == 0){//第一个,则直接申请空间 
        pHead = (char **)malloc(1*sizeof(char*));//这里是sizeof(char*),当时写成char了 
        if(pHead == NULL){//申请失败的做法 
            exit(EXIT_FAILURE);
        }
        pHead[i] =  yiji();
    } else{
        pHead = (char**)realloc(pHead,(i+1)*sizeof(char*));//这里是申请i+1而不是1 
        if(pHead == NULL){//申请失败的做法 
            exit(EXIT_FAILURE);
        }
        pHead[i] =  yiji();
    }
    i++;
    //是否继续函数引用
    bool result = isContinue();
    if(result == false){
        break;//如果不继续,则退出循环 
    }
    }
    *p = i;
    return pHead;
}
//显示最终结果
void show (char**pHead,int n){
    printf("共输入了%d个名字\n",n);
    for(int i = 0;i < n;i++){
        printf("%s\n",pHead[i]);
    }

} 
int main (void){
    int count = 0;
    char **pHead = NULL;
    pHead = shuru(&count);
    show(pHead,count);
    system("pause");
    return 0;
}

四.文件操作

为啥用?普通变量,常量,静态变量,当程序结束的时候这些变量就没了
自动申请的内存,重启电脑之后就没了
如果储存密码,就得一直存在。不管程序结束还是电脑重启,就得需要一个东西来永久保存数据
内存是为正在运行的程序分配内存。而硬盘是永久保存 比如u盘,固态硬盘。也就是说应该把密码存在硬盘里面,就应该以文件的形式存储
理解:
就是把文件内容读进程序里,然后根据格式进行解析,显示或者是修改等操作

打开文件

形式:FILE*fopen((1),(2))
原理:在物理内存开辟一块空间,将磁盘中文件的内容复制过来。我们操作的都是复制的一小份儿,本质就是一大串字符串。操作完,就要更新到磁盘空间,最终才能被修改,否则改不了。fclose()就是这个作用。
形式介绍:参数1是文件路径,参数2是文件打开方式。注意两者都要加双引号,而且文件路径里面/要变成两个
文件打开方式(只考虑文本模式)
(1)“r”:只读。文件必须存在
(2)“w”:可读可写。这是擦除写,文件不存在时可以创建文件。
(3)“a”:可读可写。这是接着写,文件不存在时可创建文件。
(4)“r+”:可读可写。相当于w,文件必须存在。
(5)“w+”:与a+和w和a基本一样
( (4)(5)基本不用)
如FILE✳fopen("","");

操作文件

1.写

形式fwrite(①,②,③,④)
①是写入文件的数据首地址
②×③ = 写入的字节数,一般②写sizeof,③写个数
④:文件指针
如:char*str = "hello";
fwrite(str,sizeof(char),strlen(str)+1,pFile);(写入的时候后面会有一个\0,而strlen读到\0结束,所以要+1.不写+1就是不写入\0)

2.读

形式fread(①,②,③,④)
①是将内容装进去的我们自己的字符数组。
②x③是读出的字节数
④是文件指针
如:FILE✳pFile = fopen("","");(注意:读数据的时候要用r或者a,不能用w)
char*str[20] = {0};
fread(str,sizeof(char),2,pFile);

3.读写结构体

(这个用fwrite和fread就可以搞定,其他的不用用)

使用示例
struct Node
{
int num;
char name[10];
};
主函数中:struct Node no = {123,"csads"};
FILE*pFile = fopen("","w");
fwrite(&no,sizeof(no),1,pFile);
fread(&no1,sizeof(no1),1,pFile);
由于结构体存在内存对齐,所以出现乱码很正常,因为有一部分空间不知道装的什么

4.关闭

这个本质就是关闭文件,更新文件,将内存空间释放
形式:fclose(File*p);

#include
#include
int main (void){
    //创建文件 
    FILE*fp = fopen("C:\\Users\\刘金豪\\Desktop\\text.txt","w");
    //写入内容
//  fputc('a',fp); 写一个字符 
    /*fputs("hello world",fp); //写一个字符串
    fgetc(fp);//读取内容,一行一行地读取
    fclose(fp);//关闭*/
    int num [5] = {1,2,3,4,5};
    //以上三行都不常用,而常用的是
    fwrite(num,sizeof(num),1,fp); //按照一定的格式写入内容,比如想把数组中的内容写到文件里面去,注意,写入的是二进制
    //图片 视频 音频 结构体基本都用这个函数来写 
    //写什么东西 ,多少字节,写几个,写到哪里去 
    fclose(fp);
    FILE*fp1 = fopen("C:\\Users\\刘金豪\\Desktop\\text.txt","r");
    int num2[5] = {};
    fread(num2,sizeof(num2),1,fp);//将文件里面的读取到num2中 
    for(int i =0;i<5;i++){
        printf("%d  ",num2[i]);
    }
    fclose(fp1);
    system("pause");
    return 0;
}

注意把读和写分开来

五.结构体:

定义:集多种类型于一身的类型,与数组同属于构造类型,不同于基本数据类型
注意:内部不要赋初始值。因为这里是定义类型,而不是变量。也就是说类的成员变量不能赋初始值。对象可以存值。
使用示例:
struct student {
int age;
char sex;
char name[10];
};
struct student xw = {18,'m',"隔壁老王"};
声明
一般在}后面就直接声明了。注意,这里全局变量默认初始化为0,而局部变量是随机的。
访问成员
结构体名字+.+访问成员的名字
xw.age = 35;
特别地注意指针
看例子就能明白
struct stu p = &stu;
printf("%d,%c,%s",p->age,p->sex,p->high);同时,有(
p).high == stu.high
注意:结构体里面不能直接放函数,但是可以通过定义函数指针来间接引用函数
如:void fun(void){
代码块}
struct Stu {
void (*p)(void);
}
在主函数中直接写
struct Stu stu = {&fun};
(stu.p)()//这里就用到了函数调用的本质:地址+参数

内存对齐(具体是字节对齐,会浪费一点空间,只不过这里是用空间换时间)

(1)32位cpu一次能处理的数据是32位,即四字节
(2)64位cpu一次能处理的数据是64位,即八字节
(3)cpu处理数据起始字节数是偶数比如 0 ~3 4~7 8~11
(4)四字节也能分为两个小块。比如第一块中第一个字节放char,第二块中两个字节放short
(5)结构体大小计算规则

①:以最大类型为字节对齐宽度,注意,是类型,而不是占的空间
②:依次填补各个成员字节,注意,是依次。
③:结尾补齐。
比如char c ; int i;double d;short s;(8为宽度)则char占了4,int占了4,double占了8,short占了8.所以为了节省空间,写结构体变量的时候尽量按字节数从小到大去写。
再比如int 和 char的一起 一共占了16个字节,只不过浪费了4个字节。
int age ;char
p;char sex;这样就24个字节
而将char*p和char sex呼唤位置之后,那就是16个字节

下面是结构体与文件操作的混合使用

#include
struct student {
int age;
char sex;
char name[10];
};

int main (void){
    struct student xw = {18,'m',"隔壁老王"};
    xw.age = 35;
    printf("%d\n",xw.age);
    struct student *p = &xw;
    //指针使用箭头来访问元素
    p->age = 300;
    printf("%d",xw.age);
    //将结构体保存到文件里面去
    //先打开文件,如果没有就创建
    FILE*fp = fopen("C:\\Users\\刘金豪\\Desktop\\text.txt","w"); 
    //将小王写入文件
    fwrite(&xw,sizeof(struct student),1,fp); 
    fclose(fp);
    //读取内容
    FILE*fp1 = fopen("C:\\Users\\刘金豪\\Desktop\\text.txt","r"); 
    struct student zs;
    fread(&zs,sizeof(xw),1,fp1);
    printf("age : %d",zs.age); 
    fclose(fp1);
    system("pause");
    return 0;
}

总结

今天讲的内容是和之前相比最多的,收获也是最多的。光整理这一篇博客,重新写代码就花了好几个小时。在整理过程中还是有很多小意外,但还是都一一克服了。感觉还是很有成就感的吧!

你可能感兴趣的:(c语言实战开发(别浪费空间)2019-12-07 Day12)