爱上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)函数重载碰到函数默认参数(尽量不要让他们相遇哦,会出错的


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

目录
相关文章
|
13天前
|
安全 C++
【c++】继承(继承的定义格式、赋值兼容转换、多继承、派生类默认成员函数规则、继承与友元、继承与静态成员)
本文深入探讨了C++中的继承机制,作为面向对象编程(OOP)的核心特性之一。继承通过允许派生类扩展基类的属性和方法,极大促进了代码复用,增强了代码的可维护性和可扩展性。文章详细介绍了继承的基本概念、定义格式、继承方式(public、protected、private)、赋值兼容转换、作用域问题、默认成员函数规则、继承与友元、静态成员、多继承及菱形继承问题,并对比了继承与组合的优缺点。最后总结指出,虽然继承提高了代码灵活性和复用率,但也带来了耦合度高的问题,建议在“has-a”和“is-a”关系同时存在时优先使用组合。
58 6
|
2月前
|
存储 算法 C++
【C++数据结构——图】图的邻接矩阵和邻接表的存储(头歌实践教学平台习题)【合集】
本任务要求编写程序实现图的邻接矩阵和邻接表的存储。需掌握带权有向图、图的邻接矩阵及邻接表的概念。邻接矩阵用于表示顶点间的连接关系,邻接表则通过链表结构存储图信息。测试输入为图的顶点数、边数及邻接矩阵,预期输出为Prim算法求解结果。通关代码提供了完整的C++实现,包括输入、构建和打印邻接矩阵与邻接表的功能。
61 10
|
2月前
|
存储 程序员 编译器
什么是内存泄漏?C++中如何检测和解决?
大家好,我是V哥。内存泄露是编程中的常见问题,可能导致程序崩溃。特别是在金三银四跳槽季,面试官常问此问题。本文将探讨内存泄露的定义、危害、检测方法及解决策略,帮助你掌握这一关键知识点。通过学习如何正确管理内存、使用智能指针和RAII原则,避免内存泄露,提升代码健壮性。同时,了解常见的内存泄露场景,如忘记释放内存、异常处理不当等,确保在面试中不被秒杀。最后,预祝大家新的一年工作顺利,涨薪多多!关注威哥爱编程,一起成为更好的程序员。
|
3月前
|
机器学习/深度学习 人工智能 自然语言处理
C++构建 GAN 模型:生成器与判别器平衡训练的关键秘籍
生成对抗网络(GAN)是AI领域的明星,尤其在C++中构建时,平衡生成器与判别器的训练尤为关键。本文探讨了GAN的基本架构、训练原理及平衡训练的重要性,提出了包括合理初始化、精心设计损失函数、动态调整学习率、引入正则化技术和监测训练过程在内的五大策略,旨在确保GAN模型在C++环境下的高效、稳定训练,以生成高质量的结果,推动AI技术的发展。
125 10
|
3月前
|
存储 缓存 算法
【C语言】内存管理函数详细讲解
在C语言编程中,内存管理是至关重要的。动态内存分配函数允许程序在运行时请求和释放内存,这对于处理不确定大小的数据结构至关重要。以下是C语言内存管理函数的详细讲解,包括每个函数的功能、标准格式、示例代码、代码解释及其输出。
137 6
|
4月前
|
存储 编译器 Linux
【c++】类和对象(上)(类的定义格式、访问限定符、类域、类的实例化、对象的内存大小、this指针)
本文介绍了C++中的类和对象,包括类的概念、定义格式、访问限定符、类域、对象的创建及内存大小、以及this指针。通过示例代码详细解释了类的定义、成员函数和成员变量的作用,以及如何使用访问限定符控制成员的访问权限。此外,还讨论了对象的内存分配规则和this指针的使用场景,帮助读者深入理解面向对象编程的核心概念。
305 4
|
5月前
|
存储 C语言
数据在内存中的存储方式
本文介绍了计算机中整数和浮点数的存储方式,包括整数的原码、反码、补码,以及浮点数的IEEE754标准存储格式。同时,探讨了大小端字节序的概念及其判断方法,通过实例代码展示了这些概念的实际应用。
292 1
|
5月前
|
存储
共用体在内存中如何存储数据
共用体(Union)在内存中为所有成员分配同一段内存空间,大小等于最大成员所需的空间。这意味着所有成员共享同一块内存,但同一时间只能存储其中一个成员的数据,无法同时保存多个成员的值。
|
5月前
|
程序员 C++ 容器
在 C++中,realloc 函数返回 NULL 时,需要手动释放原来的内存吗?
在 C++ 中,当 realloc 函数返回 NULL 时,表示内存重新分配失败,但原内存块仍然有效,因此需要手动释放原来的内存,以避免内存泄漏。

热门文章

最新文章