内容
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<stdio.h>
//常量字符串的内存空间由系统自动分配
//在常量区里面分配 当程序结束才会被释放
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<stdio.h>
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<stdio.h>
int count = 0;//当成全局变量,相对于当前文件来说的
void test3(){
count++;
printf("count = %d",count);
}
int main (){
test3();
test3();
return 0;
}
#include<stdio.h>
void test3(){
static int count = 0;//静态变量,只会被定义一次,生命周期是从程序一开始到结束,(不是开始定义到结束) 。 这里的全局变量就是相对于整个工程来说的。当然静态变量在c语言中用的很少,而在java中用的较多。
count++;
printf("count = %d\n",count);
}
int main (){
test3();
test3();
return 0;
}
二.动态分配内存
为什么需要:①:存储的数据需要延长生命周期(也就是让他被释放地晚一点)
②:一个指针变量需要存储数据,但是变量本身只能存地址,不能存数据。需要分配内存空间来存储数据。
#include<stdio.h>
#include<stdlib.h>
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 <stdio.h>
#include <stdlib.h>
#include <stdbool.h>
//从终端接收字符串 返回这个字符串的首地址
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<stdio.h>
#include<stdlib.h>
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<stdio.h>
#include<stdlib.h>
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<stdlib.h>
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;
}
总结
今天讲的内容是和之前相比最多的,收获也是最多的。光整理这一篇博客,重新写代码就花了好几个小时。在整理过程中还是有很多小意外,但还是都一一克服了。感觉还是很有成就感的吧!