爱上c++的第一天:内存存储模型,引用和函数方面的知识

简介: 在运行的时候调用程序分配内存可以在任何时候决定分配内存即分配的大小,用户自行决定在何时释放。堆中的所有东西都是匿名的,不能用名字访问,只能通过指针来访问。

你的c++学习路上明灯


今天我们开始学习c++的第一篇文章,讲的也是比较简单,不知道有没有人能坚持下来,陪我一起度过这个寒假,害,加油吧!


好了,我们开始今天的学习。


一,内存存储模型


6907e09ba5ef4f59aad349255130efab.png


首先我们要知道的是,在c++程序执行前后,系统会将内存大方向划分为4个区域


c++在程序运行前分为全局区和代码区


程序运行后分为栈区和堆区


一.代码区:存放函数体的二进制代码,由操作系统进行管理


存放的就是CPU执行的机器指令


代码区有两个特点


1.共享性


目的是对于频繁被执行的程序,只需在内存中有一份代码即可。


2.只读性


防止程序在执行时被意外的修改,造成不必要的麻烦


二,全局区:存放全局变量和静态变量以及常量


存放的内容有全局变量和静态变量和常量(字符串常量,const 修饰的全局变量(全局常量))


788afbabcf9747a1823ab4572d5b4aae.png


三,栈区和堆区


我之所以将这两个概念放在一起讲,一方面是因为我们之前就已经对这些概念有过了解,我们也经常接触这些东西,而且二者比较相近,经常放在一起解释了解


1.定义:堆栈是一种数据结构,具体是一个特定的存储区域或寄存器,是一种数据项排列的数据结构


2.栈区:


1)只能在一端(栈顶)对数据项进行插入和删除等操作,但另一端是固定的,栈顶是浮动的,(先进后去)


2)栈顶地址总是小于栈的基地址。


3)存放自动变量,函数执行结束后这些存储单元由编译器自动释放


4)由系统自动分配空间


3.堆区(自由存储区)


1)在运行的时候调用程序分配内存可以在任何时候决定分配内存即分配的大小,用户自行决定在何时释放。堆中的所有东西都是匿名的,不能用名字访问,只能通过指针来访问。


2)需要程序员自己申请,并指明大小


p=(char*)malloc(10)


例如上面的式子,在堆区中开辟了一片空间,用指针p指向这一片空间,p是在栈区中的,申请得来的空间在堆区。


四,使用new开辟空间


格式:数据类型 new int(数据)


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int * test() {
  //在堆区创建一块空间,存放一个整型数据
  int* p = new int(20);
  return p;
}
int* test1() {
  //存放一个数组,数组中含十个整型
  int* p = new int[10];
  for (int i = 0; i < 10; i++) {
    p[i] = i;
  }
  return p;
}
int main() {
  int* p = test();
  int* pc = test1();
  cout << *p << endl;
  for (int i = 0; i < 10; i++) {
    cout << pc[i] << endl;
  }
  //二者的释放形式不同,要注意
  delete[]pc;
  delete p;
  return 0;
}


二,引用


a0a060eb08f94d54856a6f81c03e31e2.png


 一,引用的定义和本质


1.引用就是给变量起别名,可以把它理解为指针,因为它的本质就是一个指针常量(指向不能变)


2.定义格式:数据类型 &别名=原名;


3.特点


1)引用必须初始化


2)引用在初始化后,不可以改变


4.引用的本质


本质就是指针常量,只是所有指针的操作都由编译器做了而已


补:指针常量和常量指针


前者是指针的指向不能改变,本质是一个常量,常量的特性体现在了指针上,也就是指针的指向。


后者是指针的值不能改变,本质是一个指针,指针指向的变量体现了常量的特性,值不能改变,例如数组。


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int main() {
  int a = 10;
  int& b = a;
  int& c = a;
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  cout << "c = " << c << endl;
  int d = 20;
  b = d;//赋值操作,并不是更改引用的操作
  cout <<'\n' << "d = " << d << endl;
  cout << "b = " << b << endl;
  return 0;
}


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
void fun(int& ref) {
  //ref是引用,自动转换为*ref=100;
  ref = 100;
}
int main() {
  int a = 10;
  //自动转换为int const* ref=&a;指针常量的指向不可变,也说明了引用初始化之后为什么不能改变
  int& ref = a;
  //自动转换为*ref=20;
  ref = 20;
  cout << "a = " << a << endl;
  cout << "ref = " << ref << endl;
  fun(a);
  cout << "ref = " << ref << endl;
  return 0;
}


二,引用做函数参数


函数传参时,可以利用引用的技术让形参改变实参


优点:可以简化指针


           修改实参


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
//交换函数
//1.值传递
void swap1(int a, int b) {
  int temp = a;
  a = b;
  b = temp;
}
//2.地址传递
void swap2(int* a, int* b) {
  int temp = *a;
  *a = *b;
  *b = temp;
}
//3.引用传递
void swap3(int& a, int& b) {
  int temp = a;
  a = b;
  b = temp;
}
int main() {
  int a = 10;
  int b = 20;
  swap1(a, b);
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  swap2(&a, &b);
  putchar('\n');
  cout << "a = " << a << endl;
  cout << "b = " << b << endl; swap1(a, b);
  swap3(a, b);
  putchar('\n');
  cout << "a = " << a << endl;
  cout << "b = " << b << endl;
  return 0;
}
//我认为,引用就是类似于指针的一个概念。


三,引用做函数的返回值


注意:


1.不要返回局部变量的引用


这就是一个普通的野指针问题,你要是不懂的话就说明基础还不行呀!要努力了


2.函数的调用可以作为左值

#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int& test() {
  static int a = 10;
  return a;
}
int main() {
  int& b = test();
  cout << b << endl;
  //以引用为函数返回值的函数的调用可以作为左值
  //像此处的函数返回值是一个引用,本质上还是一个int型的变量,所以可以作为左值使用。
  test() = 1000;
  cout << b << endl;
  return 0;
}


左值必须是一个可以修改的变量。


四,常量引用


差点忘了这个,嘿嘿嘿


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int main() {
  int a = 10;
  //等价于int temp=10; const int &ref=temp;
  //temp为系统帮取的变量名,也不一定是temp;
  //加上const之后,变量的值就不能被修改了,变为了只读状态
  const int& ref = 10;
  //引用需要一个合法的内存空间
  //int& ref = 10;例如这样的操作就是错误的
  return 0;
}


什么叫常量引用,就是给一个常量起别名,但是这样做是不行的,为什么加上const就对了?


加上const后,这个常量的指向就是一定的,之前为什么不行?就是因为指向不定,编译器不懂这个常量到底是哪里来的,加上const后,编译器会自动给这个常量分配一个空间,以支持语法的正确性。


三,函数的高级操作


77ef2e26e45041bb88737c7df71cd7c3.png


 一,函数默认参数


1)如果某个位置已经有了默认参数,那么从这个位置往后,从左往右都必须有默认参数


2)如果函数声明有默认参数,函数实现就不能有默认参数


声明和实现只能有一个阶段有默认参数


不然编译器会报错,变量不能反复定义


**正确形式如下:


1fe749a80f2d4001a3479056b7cd1516.png


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
int func(int a, int b=2, int c=3) {
  return a + b + c;
}
int main() {
  cout << func(1) << endl;
  return 0;
}
//1.如果某个位置已经有了默认参数,那么从这个位置开始往后,从左往右都必须有默认值
//2.如果函数声明有默认参数,函数实现就不能有默认参数,不然就会产生二义性
//即对变量实现了多次定义。(声明和实现只能有一个默认参数)


二,函数占位参数


函数占位参数,顾名思义,就是用来占位的参数,调用函数时必须填补该位置


函数占位参数也能是函数默认参数


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
void func(int a, int) {
  cout << "this is function" << endl;
}
int main() {
  int a = 10;
  func(a,10);
  return 0;
}
//函数占位参数也可以有默认参数
void func(int a, int = 10) {
  cout << "this is function" << endl;
}
int main() {
    int a = 10;
    func(a);
    return 0;
  }


三,函数重载


函数名可以相同,用来提高复用性(我现在也不知道这样有啥用,后面慢慢学)


1.条件


1)在同一个作用域下


2)函数名相同


3)函数参数类型不同/顺序不同/个数不同


这段主函数部分代码不完整,是留给你们自己调试证明的


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
void func() {
  cout << "this is func" << endl;
}
void func(int a) {
  cout << "this is func(int a)" << endl;
}
void func(double a) {
  cout << "this is func(double a)" << endl;
}
void func(int a,double b) {
  cout << "this is func(int a,double b)" << endl;
}
void func(double a,int b) {
  cout << "this is func(double a,int b)" << endl;
}
int main() {
  func();
  return 0;
}


注意事项:


函数的返回值不可以作为函数重载的条件


即,仅仅只有返回值不同,其他的条件都相同,这样是不算函数重载的


无法重载仅按返回类型区分的函数


#define _CRT_SECURE_NO_WARNINGS 1
#include<iostream>
using namespace std;
//1.引用作为重载条件
void func(int& a) {
  cout << "this is func(int& a) " << endl;
}
void func(const int& a) {
  cout << "this is func(const int& a) " << endl;
}
//2.函数重载碰到函数默认参数
void func1(int a, int b = 10) {
  cout << "this is func1(int a, int b = 10)" << endl;
}
void func1(int a) {
  cout << "this is func(int a) " << endl;
}
int main() {
  func(10);
  return 0;
}


那这是为什么呢?


嘿嘿嘿,我们来分析一些,我们既然写了这个函数,是不是一定会去使用它,那么当我们使用的时候会写该函数的返回类型吗?当然不会啦,那让编译器怎么办?这不是为难人家吗?哈哈哈


2.特殊情况


1)引用作为重载条件


2)函数重载碰到函数默认参数(尽量不要让他们相遇哦,会出错的


好了,今天的学习就到这里了,嘿嘿嘿,你们学到了多少呢?加油哦,别放弃哦!!!

目录
相关文章
|
22天前
|
机器学习/深度学习 算法 物联网
大模型进阶微调篇(一):以定制化3B模型为例,各种微调方法对比-选LoRA还是PPO,所需显存内存资源为多少?
本文介绍了两种大模型微调方法——LoRA(低秩适应)和PPO(近端策略优化)。LoRA通过引入低秩矩阵微调部分权重,适合资源受限环境,具有资源节省和训练速度快的优势,适用于监督学习和简单交互场景。PPO基于策略优化,适合需要用户交互反馈的场景,能够适应复杂反馈并动态调整策略,适用于强化学习和复杂用户交互。文章还对比了两者的资源消耗和适用数据规模,帮助读者根据具体需求选择最合适的微调策略。
|
21天前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
44 1
|
26天前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
27天前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。
|
30天前
|
存储 弹性计算 算法
前端大模型应用笔记(四):如何在资源受限例如1核和1G内存的端侧或ECS上运行一个合适的向量存储库及如何优化
本文探讨了在资源受限的嵌入式设备(如1核处理器和1GB内存)上实现高效向量存储和检索的方法,旨在支持端侧大模型应用。文章分析了Annoy、HNSWLib、NMSLib、FLANN、VP-Trees和Lshbox等向量存储库的特点与适用场景,推荐Annoy作为多数情况下的首选方案,并提出了数据预处理、索引优化、查询优化等策略以提升性能。通过这些方法,即使在资源受限的环境中也能实现高效的向量检索。
|
1月前
|
存储 编译器
数据在内存中的存储
数据在内存中的存储
41 4
|
1月前
|
存储 前端开发 C++
C++ 多线程之带返回值的线程处理函数
这篇文章介绍了在C++中使用`async`函数、`packaged_task`和`promise`三种方法来创建带返回值的线程处理函数。
45 6
|
1月前
|
存储 Java
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
这篇文章详细地介绍了Java对象的创建过程、内存布局、对象头的MarkWord、对象的定位方式以及对象的分配策略,并深入探讨了happens-before原则以确保多线程环境下的正确同步。
53 0
JVM知识体系学习四:排序规范(happens-before原则)、对象创建过程、对象的内存中存储布局、对象的大小、对象头内容、对象如何定位、对象如何分配
|
1月前
|
C++
C++ 多线程之线程管理函数
这篇文章介绍了C++中多线程编程的几个关键函数,包括获取线程ID的`get_id()`,延时函数`sleep_for()`,线程让步函数`yield()`,以及阻塞线程直到指定时间的`sleep_until()`。
23 0
C++ 多线程之线程管理函数
|
1月前
|
存储 C语言
深入C语言内存:数据在内存中的存储
深入C语言内存:数据在内存中的存储