数据类型-变量-内存四区-指针

1、内存四区

1.1、数据类型的本质

1)数据类型基本概念

  • 类型是对数据的抽象
  • 类型相同的数据具有相同的表示形式、存储格式、相关的操作
  • 程序中使用的数据必定属于某种数据类型
  • 数据类型和内存 有关系
  • C/C++ 引入数据类型,可以更方便地管理数据

2)数据类型的本质

  • 数据类型可以理解为创建变量的模具:固定内存大小的别名
  • 数据类型的作用:编译器预算对象(变量)分配的内存空间大小
  • 数据类型只是模具,编译器不为类型分配空间,只有根据类型创建的变量才会分配空间
int main()
{int a;			//告诉编译器分配 4 个字节int b[10];		//告诉编译器分配 4*10 个字节//类型的本质是固定大小内存的别名printf("sizeof(a)=%d\nsizeof(b)=%d\n",sizeof(a),sizeof(b));		//4 40//打印地址printf("b:%d	&b:%d\n",b,&b);		//数组名字就是数组首元素地址,数组首地址		两个输出一样的//b 和 &b 的数据类型不一样//b,数组首元素地址,一个元素 4 字节,+1 -> +4//&b,整个数组的首地址,一个数组 4*10=40字节,+1 -> +40printf("b+1:%d	&b+1:%d\n",b+1,&b+1);		//14678444 14678480//指针类型长度,32位为4, 64位为8char *****************p=NULL;int *q=NULL;printf("%d %d\n",sizeof(p),sizeof(q));		//32位:4 4		64位:8 8return 0;
}

3)数据类型的别名

  • typedef 可以给类型取别名,且 typedef 只能给类型取别名
typedef unsigned int u32;//typedef 通常和结构体一起使用
typedef struct	myStruct		//这里的 myStruct 可写可不写
{int a;int b;
}TMP;
int mian()
{u32 t;				//unsigned int t;return 0;
}

4)void 类型(空类型、无类型)

  • 函数参数为空,在定义函数的时候,可以使用 void 来修饰:int func(void); 在 C++ 中 void 写不写是一样的,但是在 C 中是存在区别的
  • 函数没有返回值,使用 void 来修饰:void func(void);
  • 不能定义 void 类型的普通变量:void a; //err
  • 可以定义 void 类型的指针*:void* p;
    • 主要是因为 void 类型的普通变量不同类型的内存大小不一样,在编译器分配内存空间的时候无法确定分配的内存大小,而在相同的操作系统下,不同类型的指针变量内存大小相同,不影响编译器的内存分配
  • void p 万能指针*,常用作函数返回值,或者函数的参数
    • 这样可以很灵活,只要是指针就可以使用,例如 molloc 函数的定义:void* molloc(size_t size)
    • memcpy 函数,拷贝内存的内容,可以拷贝各种类型的数组,而 strcpy,只能拷贝 char 类型的数组
1.2、变量的使用
  • C 语言中,一维数组、二维数组其实也是有数据类型的
  • C 语言中,函数也是具有数据类型的,可以通过函数指针进行重定义

1)变量的本质

  • 变量:既能度又能写的内存对象。一旦初始化后不能修改的称为称量
  • 变量定义形式:
    • 类型 标识符1,标识符2,…,标识符n
  • 变量的本质是:一段连续内存空间的别名
int main()
{int a;//变量相当于门牌号,内存相当于房间//直接赋值a=10;pringf("a=%d\n",a);		//10//间接赋值pringf("&a=%p\n",&a);		// a的地址p=&a;pringf("p=%p\n",p);		// a的地址*p=22;pringf("a=%d\n",a);		//22pringf("*p=%d\n",*p);		//22return 0;
}
1.3、内存四区模型
  • 四区:栈区、堆区、全局区、代码区(不用管)

1)全局区(静态区):全局变量、静态变量、文字常量

  • 全局变量和静态变量,初始化的全局变量和静态变量存储在一起,未初始化的全局变量在另一块
  • 全局区相同的常量只存在一份
char *get_str1()
{char *p="abcdef";		//文字常量区return p;
}char *get_str1()
{char *q="abcdef";		//文字常量区return q;
}int main()
{char *p=NULL;char *q=NULL;p=get_str1();//%s,打印指针指向的内存区域的内容//%d,打印 p 本身的值printf("p=%s, p %d\n",p,p);			//p=abcdef,p=一串地址q=get_str2();printf("q=%s, q %d\n",q,q);			//q=abcdef,q=一串地址,且这个地址和p的地址一样//主函数里面的p和 get_str1里面的p对应不同的内存//全局区相同的常量只存在一份,因此主函数里面的 p 和 q 指向同一块内存return 0;
}

2)栈区

char* get_str()
{								//字符串 "sdskcnckjana" 是存放在全局区char str[]="sdskcnckjana";		//栈区,函数结束,内存销毁,主函数中复制内存的内容,因此复制到的内容是不确定的,可能是原本的内容,也可能是乱码//这里在 char str[]="sdskcnckjana" 之后,会拷贝一份字符串到栈区return str;
}int main()
{char* buffer[128]={0};strcpy(buffer,get_str());printf("%s\n",buffer);		//打印的结果:不确定,即乱码,这里还有可能输出 sdskcnckjana,是因为这里拷贝的时候可能 get_char 还没有销毁return 0;
}
char* get_str()
{								//字符串 "sdskcnckjana" 是存放在全局区char str[]="sdskcnckjana";		//栈区,函数结束,内存销毁,主函数中复制内存的内容,因此复制到的内容是不确定的,可能是原本的内容,也可能是乱码printf("%s\n",buffer);		//打印的结果:sdskcnckjana//这里在 char str[]="sdskcnckjana" 之后,会拷贝一份字符串到栈区return str;
}int main()
{char* buffer[128]={0};char* p=NULL;p=get_str();printf("%s\n",buffer);		//打印的结果:不确定,即乱码return 0;
}

3)堆区

char* get_str()
{								//字符串 "sdskcnckjana" 是存放在全局区char str[]="sdskcnckjana";		//栈区,函数结束,内存销毁,主函数中复制内存的内容,因此复制到的内容是不确定的,可能是原本的内容,也可能是乱码printf("%s\n",buffer);		//打印的结果:sdskcnckjana//这里在 char str[]="sdskcnckjana" 之后,会拷贝一份字符串到栈区return str;
}char* strget2()
{char *temp=(char*)malloc(100);		//堆区分配空间if(temp==NULL)return NULL;strcpy(tmp,"snjcscsdmkcs");//在这里,字符串"snjcscsdmkcs"存放在全局区,temp存放在栈区,指向一块堆区的内存,strcpy之后,会拷贝一份字符串"snjcscsdmkcs"到temp指向的堆区内存//get_str2函数运行完毕之后,p也会指向temp指向的堆区内存,并且会释放指针temp,但是temp指向的内存不会释放,需要手动释放return temp;
}
int main()
{char* buffer[128]={0};char* p=NULL;p=get_str2();if(!p){printf("%s\n",buffer);		//打印的结果:snjcscsdmkcsfree(p);		//释放p之前,这块堆内存使用权归p,释放之后,使用权归操作系统,但是内部的内容依然存在,直到下次被写才会改变,并且释放p之后,p依然指向这块堆区域,只是p指向的堆内存可以由系统支配了,所以一般指针释放之后,会将其指向空指针p=NULL;}return 0;
}
1.4、函数调用模型
  • 关注重点在于调用的流程和变量的生命周期
  • 调用模型是一个栈模型:先调用,后返回

外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传

1.5、函数调用变量传递分析
  • main 函数调用子函数1,子函数1调用子函数2,那么 main 函数在栈区开辟的内存,子函数1和子函数2都可以使用
  • main 函数在堆区开辟的内存,没有释放的时候,子函数1和子函数2都可以使用
  • 子函数1在栈区开辟的内存,子函数1和子函数2都可以使用,但是 main 函数无法使用
  • 子函数在堆区开辟的内存,没有释放的时候,main 函数、子函数1和子函数2都可以使用
  • 全局区存放的变量,生命周期和程序一致,因此无论哪个函数在全局区开辟的内存,所有函数都可以使用
1.6、静态局部变量的使用
int *getA()
{static int a=10;	//a是一个局部的静态变量,函数结束,内存不释放,因此只要把地址传出去,就可以通过地址使用这个内存了return &a;
}int main()
{int *p=getA();		//通过地址使用局部静态变量return 0;
}
  • 在变量的生命周期之外,只要内存没有释放,就能够通过一定的手段使用对应的内存
1.7、栈的生长方向和内存释放方向
  • 栈底,高地址;栈顶,低地址。栈的生长方向:栈底到栈顶,即栈的高地址到低地址,一般描述为从上到下
  • 堆的生长方向与栈相反,从低地址到高地址,一般描述为从下到上
  • 栈内的数组内部,是从低地址向高地址的,即栈内数组内部,也是从下到上

2、指针强化

2.1、指针也是一种数据类型
  • 指针变量也是一种变量,占有内存空间,用来保存内存地址
  • 通过星号操作内存
    • 在指针声明的时候,星号表示所声明的是指针变量
    • 在指针使用的时候,星号表示操作指针所指向的内存空间中的值
    • *p 相当于通过地址(p 变量的值)找到一块内存,然后操作内存
int main()
{int a=100;int *p=NULL;int p1=NULL;char *********q=0x1111;//指针指向谁,就把谁的地址赋值给指针p1=&a;//通过星号可以找到指针指向的内存区域,操作的还是内存//星号放在等号的左边,给内存赋值,写内存//星号放在等号的右边,取内存赋值,读内存*p1=22;printf("%d %d\n",sizeof(p),sizeof(q));		//32位系统:4 4return 0;
}
2.2、指针间接赋值
  • void* 类型的指针在使用的时候需要转换成实际的类型
int main()
{void* p;char buf[1024]="aancnciwce";p=buf;//void* 类型的指针在使用的时候需要转换成实际的类型printf("%s\n",(char*)p);int a[100]={1,2,3,4};p=a;int i=0;//void* 类型的指针在使用的时候需要转换成实际的类型for(i=0;i<4;i++){printf("%d ",*((int*)p+i));}int b[3]={1,2,3};int c[3];memcpy(c,b,sizeof(b));		//void* 类型的指针转换成了 int*for(i=0;i<3;i++){printf("%d ",c[i]);}char *q=NULL;			//#define NULL ((void*)0),这里实际上q没有具体的指向,所以给q指向的内存赋值就会报错/*//加上这样两句,就可以给q 一个具体的指向,再给其指向的内存赋值,就没问题了char str2[100]={0};q=str2;*///给 q 指向的内存区域赋值strcpy(q,"1234");		//errreturn 0;
}
  • 分文件编程说明
//防止头文件重复包含
#pragma once//例如,有两个头文件 a.h 和 b.h,且在 a.h 中包含了 b.h,在 b.h 中包含了 a.h,那么会出现头文件包含的死循环,导致文件包含过多的错误//不添加兼容 C++,使用 C++ 语言的程序调用 C 语言的程序的语句,编译的时候不会有问题,但是使用的时候会有问题。添加了之后,可以不做任何改动,直接使用
  • 复习:
    • 数据类型本质是固定内存大小的别名
    • typedef,给数据类型起别名
    • 栈和堆,栈是为了效率,堆为了内存分配更加灵活
    • 栈的分配和回收由系统进行,堆的分配和回收由程序员进行
    • 数组做形参,退化为指针。数组作为形参,丢失长度信息,使用 sizeif(a)/sizeof(a[0]) 无法计算出数组长度
    • strcpy(p,“abcdefg”); 实际上不是给指针赋值,而是把字符串 “abcdefg” 拷贝到指针 p 指向的内存空间*
  • 字符串,通过首地址可以用 printf 打印出来,而数组不可以,是因为字符串末尾有字符串结束符,而数组没有
  • 指针变量和指针指向的内存是两个不同的概念
    • 改变指针变量的值,会改变指针的指向,但是不会改变指针指向的内存的内容
    • 改变指针指向的内存的内容,不会改变影响到指针变量的值
  • 使用指针写内存的时候,一定要确保内存可写
char *buf="nscnscnwsicw";	//指针直接指向文字常量区
buf2[2]='l';			//err,因为这个字符串存放在文字常量区,内容不可改char str[]="nsijacnicwc";	//字符串常量本身存放在文字常量区,但是由于字符数组赋值,会复制一份存放在栈区
str[2]='l';				//OK,由于 str 是存放在栈区的字符数组,因此是可修改的
  • 指针是一种数据类型,是指它指向的内存空间的数据类型

    • 指针步长 (p++),根据指针所指向的内存空间的数据类型来确定
    p++ 等价于 (unsigned char)p+sizeof(a);
    
  • 不允许向 NULL 和未知非法地址拷贝内存

char *p3=NULL;
strcpy(p3,"lll");		//err,如果给 p3 赋值为某一个具体的非法地址,如 0x0001,也会出错,因为这个内存不允许使用
//给 p3 指向的内存区域拷贝内存,但是 p3 为空,没有指向任何有效的内存,因此内存拷贝会出错
2.3、通过指针间接赋值
  • 步骤:
    • 一般变量和指针变量
    • 建立关系
    • 通过 * 操作内存
int main()
{int a=100;int *p=NULL;//建立关系,指针指向谁,就把谁的地址赋给指针了p=&a;//通过 *  操作内存;*p=32return 0;
}
  • 如果想通过形参改变实参的值,必须地址传递
int get_a()
{int a=10;return a;
}void get_a2(int a)
{a=22;
}void get_a3(int *a)
{*a=33;		//通过星号操作内存
}void get_a4(int *a1,int *a2,int *a3,int *a4)
{*a1=33;		//通过星号操作内存*a2=44;*a3=55;*a4=66;
}int main()
{int a=get_a();printf("%d\n",a);		//输出为:10get_a2(a);printf("%d\n",a);		//输出为:10//如果想通过形参改变实参的值,必须地址传递//实参,形参get_a2(a);				//在函数调用时,建立关系printf("%d\n",a);		//输出为:33int a1,a2,a3,a4;get_a4(&a1,&a2,&a3,&a4);printf("%d %d %d %d\n",a1,a2,a3,a4);return 0;
}
  • 间接赋值是指针的最大意义,尤其是配合函数使用的时候
  • 二级指针间接赋值
void func1(int *p)
{p=0xaabb;printf("%p\n",p);		//0000aabb
}void func2(int **p)
{*p=0xeeff;				//需要深入理解printf("%p\n",p);		//0000eeff
}int main()
{/*//一个变量应该定义一个什么类型的指针保存它的地址//在原来的基础上再多加一个*int a=10;int *p=&a;int **q=&p;int *********t=NULL;int **********tp=&t;*/int *p=0x1122;printf("%p\n",p);		//00001122func1(p);				//值传递,传递的是指针变量的值printf("%p\n",p);		//000011222func2(&p);				//地址传递,传递的是指针变量的地址printf("%p\n",p);		//0000eeffreturn 0;
}
2.3、指针作为函数参数的输入输出特性
  • 主调函数可以把堆区、栈区、全局数据内存地址传给被调函数
  • 被调函数只能返回堆区、全局数据
  • 指针作为函数参数具有输入输出特性:
    • 输入:主调函数分配内存
    • 输出:被调函数分配内存
void func(char* p)
{//给p指向的内存区域拷贝,实际上就是main中的bufstrcpy(p,"ssvcscac");
}void func1(char **p,int *len)
{if(p==NULL)return;char* tmp=(char*)malloc(100);if(tmp==NULL)return;strcpy(tmp,"cscnsncscna");//间接赋值*p=tmp;*len=strlen(tmp);    
}int main()
{//输入:主调函数分配内存char buf[100]={0};func(buf);printf("%s\n",buf);		//ssvcscacchar *p=NULL;func(p);		//err,不能给空或者非法未知内存拷贝//输出:被调用函数分配内存,要想进行内存修改,必须进行地址传递char *p1=NULL;int len=0;func1(&p1,&len);if(p)printf("%s	%d\n",p,len);		//cscnsncscna 11return 0;
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/581491.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

laravel5.8中实现验证码组件的安装和验证

本篇文章主要讲解使用laravel5.8自带的验证码库实现验证码验证的效果教程。通过本教程你可以快速接入到自己的项目中开发相应的验证功能。 作者&#xff1a;任聪聪 (rccblogs.com) 日期&#xff1a;2023年12月17日 实际效果 安装步骤 步骤一、输入命令 composer require mews…

代理模式:中间者的故事

代理模式&#xff1a;中间者的故事 介绍需求分析代理模式代码实现代理模式整理和用途第一种用途第二种用途第三种用途第四种用途 总结 介绍 本文引用《大话设计模式》第七章节的内容进行学习分析&#xff0c;仅供学习使用 需求&#xff1a;小明拜托自己好朋友小王给他朋友小美…

Swift函数式编程——函数

目录 Swift函数式编程&#xff0d;函数 高阶函数&#xff08;Higher order function&#xff09; 一等函数&#xff08;First class function&#xff09; 闭包 函数柯里化&#xff08;Function Curring&#xff09; 函数式思维 使用函数解决问题 使用函数组合 总结 Sw…

解决IDEA 不能正确识别系统环境变量的问题

问题描述 本人laptop 上的是设置了GOOGLE_APPLICATION_CREDENTIALS 这个环境变量的&#xff0c; 正常java or python 的程序能基于这个环境变量使用 某个gcp service account 去访问GCP的资源 [gatemanmanjaro-x13 ~]$ env | grep -i google GOOGLE_APPLICATION_CREDENTIALS/…

2023年,写博客带给我的收获与成长

文章目录 前言写博客的心路历程膜拜写博客大佬博客大佬带来的诱惑尝试写博客坚持写博客 决定写博客的原因2023年写博客的成就博客的创作粉丝的增长博客专家成就商务合作 2024年对技术写作的展望 前言 没错&#xff0c;我就是那个考试睡大觉、作文空白交卷的王二蛋。面对写作&a…

SpringBoot2.7-集成Knife4j

Knife4j 是什么 Knife4j是一个集Swagger2 和 OpenAPI3为一体的增强解决方案 添加依赖 <!--引入Knife4j的官方start包,该指南选择Spring Boot版本<3.0,开发者需要注意--> <dependency><groupId>com.github.xiaoymin</groupId><artifactId>knif…

Django Cookie和Session使用(十一)

一、Cookie Cookie具体指一小段信息&#xff0c;它是服务器发送出来存储在浏览器上的一组键值对&#xff0c;下次访问服务器时浏览器会自动携带这些键值对&#xff0c;以便服务器提取有用信息。 Cookie的特性 1、服务器让浏览器进行设置的 2、保存在浏览器本地&#xff0c;…

免费API-JSONPlaceholder使用手册

官方使用指南快速索引>>点这里 快速导览&#xff1a; 什么是JSONPlaceholder?有啥用?如何使用JSONPlaceholder? 关于“增”关于“改”关于“查”关于“删”关于“分页查”关于“根据ID查多个” 尝试自己搭一个&#xff1f;扩展的可能&#xff1f; 什么是JSONPlaceho…

面向对象(高级)知识点强势总结!!!

文章目录 一、知识点复习1-关键字&#xff1a;static1、知识点2、重点 2-单例模式&#xff08;或单子模式&#xff09;1、知识点2、重点 3-理解main()方法1、知识点2、重点 4-类的成员之四&#xff1a;代码块1、知识点2、重点 5-关键字&#xff1a;final1、知识点2、重点 6-关键…

新建虚拟环境并与Jupyter内核连接

第一步:在cmd里新建虚拟环境,shap38是新建的虚拟环境的名字 ,python=3.x conda create -n shap38 python=3.8第二步,安装ipykernel,打开anconda powershell prompt: 虚拟环境的文件夹位置,我的如图所示: 进入文件夹并复制地址: 输入复制的文件夹地址更改文件夹:…

交换域系数的选择:图像处理与编码的关键策略

在图像处理和编码领域&#xff0c;选择适当的交换域系数对于实现高效的图像处理和编码至关重要。交换域系数是指在特定的数学变换下产生的频域系数。通过选择合适的交换域系数&#xff0c;可以实现图像的压缩、增强和重构。本文将深入探讨交换域系数的选择在图像处理和编码中的…

你好!Apache Seata

北京时间 2023 年 10 月 29 日&#xff0c;分布式事务开源项目 Seata 正式通过 Apache 基金会的投票决议&#xff0c;以全票通过的优秀表现正式成为 Apache 孵化器项目&#xff01; 根据 Apache 基金会邮件列表显示&#xff0c;在包含 13 个约束性投票 (binding votes) 和 6 个…

Qt学习:Qt的意义安装Qt

Qt 的简介 QT 是一个跨平台的 C图形用户界面应用程序框架。它为程序开发者提供图形界面所需的所有功能。它是完全面向对象的&#xff0c;很容易扩展&#xff0c;并且允许真正地组件编程。 支持平台 xP 、 Vista、Win7、win8、win2008、win10Windows . Unix/Linux: Ubuntu 等…

【ARMv8M Cortex-M33 系列 2.1 -- Cortex-M33 使用 .hex 文件介绍】

文章目录 HEX 文件介绍英特尔十六进制文件格式记录类型hex 示例Cortex-M 系列hex 文件的使用 HEX 文件介绍 .hex 文件通常用于微控制器编程&#xff0c;包括 ARM Cortex-M 系列微控制器。这种文件格式是一种文本记录&#xff0c;用于在编程时传递二进制信息。.hex 文件格式最常…

docker学习笔记02-安装mysql

1.安装mysql8 下载MySQL镜像 docker pull mysql:8.0创建并启动容器 docker run -itd --name mysqltest -p 9999:3306 -e MYSQL_ROOT_PASSWORD123456 mysql其中-it是交互界面 -d是后台执行 -name 指定容器名称 -p指定映射端口 -e设置环境变量 最后mysql是镜像名或者用镜像id如…

Flask 日志

flask 日志 代码源码源自编程浪子flask点餐小程序代码 记录用户访问日志 和 错误日志 这段代码是一个基于Flask框架的日志服务类&#xff0c;用于 记录用户访问日志 和 错误日志。代码中定义了一个名为LogService的类&#xff0c;其中包含了两个静态方法&#xff1a;addAcc…

XUbuntu22.04之删除多余虚拟网卡和虚拟网桥(二百零四)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

用Xshell连接虚拟机的Ubuntu20.04系统记录。虚拟机Ubuntu无法上网。本机能ping通虚拟机,反之不能。互ping不通

先别急着操作&#xff0c;看完再试。 如果是&#xff1a;本机能ping通虚拟机&#xff0c;反之不能。慢慢看到第8条。 如果是&#xff1a;虚拟机不能上网&#xff08;互ping不通&#xff09;&#xff0c;往下一直看。 系统是刚装的&#xff0c;安装步骤&#xff1a;VMware虚拟机…

【Linux】 last 命令使用

last 命令 用于检索和展示系统中用户的登录信息。它从/var/log/wtmp文件中读取记录&#xff0c;并将登录信息按时间顺序列出。 著者 Miquel van Smoorenburg 语法 last [-R] [-num] [ -n num ] [-adiox] [ -f file ] [name...] [tty...]last 命令 -Linux手册页 选项及作用…

vue项目表单使用正则过滤ip、手机号

import useFormValidate from /hooks/useFormValidatesetup(props, { emit }) {const { validateName, validateIPAndPort } useFormValidate()const state reactive({workFaceInfo: props.info?.id ? props.info : {},sysTypeData: props.sysType,formRules: {name: [{req…