变量的概念
数据类型
字面值常量: 直接在代码中表示的值,它们不需要声明即可使用
1.定义提供一个具名的、可供程序操作的存储空间(内存中),用以临时存储输入数据、中间结果和最终结果。
属性变量有三个重要属性:名称、值、类型
C++区分了声明和定义
变量的声明:用于向程序表明变量的类型和名字
变量的定义 用于为变量分配存储空间,还可以为变量指定初始值
C++内存模型:--编译器和操作系统 用户态的内存地址
全局/静态存储区(Global/Static Storage)
栈(Stack) 栈内存用于存储局部变量和函数调用信息
堆(Heap) 堆内存用于存储动态分配的对象
常量存储区(Constant Storage
2.从数据的角度--变量指的是在程序运行中,可以发生变化的量;是存放数据值的容器
常量
变量名 计算机语言中能储存计算结果或能表示值的抽象概念
定义和声明
定义和初始化 赋值
初始化--赋值初始化, 函数返回值初始化
在类外定义非内联成员函数。
声明
extern int b; //声明,不是定义
extern int b = 20; //是定义 显示初始化的声明即定义
int a = 10; //定义就是声明
变量声明 函数声明 类声明
定义 不仅声明了名字,还实际分配了内存空间(对于变量)
或提供了具体的实现(对于函数、类等) 函数原型是一个声明,而函数的实现则是定义
声明仅仅是将一个符号引入到一个作用域。而定义提供了一个实体在程序中的唯一描述
将具有外部链接的定义放在头文件中几乎都是编程错误。因为如果该头文件中被多个源文件包含,那么就会存在多个定义,链接时就会出错。
###分离式编译机制
文件间共享代码的方式-- 将声明和定义区分开来
初始化和赋值
初始化 Initialization
是指在变量或对象被创建时,给它赋予一个初始的值。初始化是分配内存并为变量或对象提供初始值的过程,通常是在声明变量或创建对象时发生的
变量初始化: 基本类型变量初始化 指针初始化
对象初始化: 调用类的构造函数来初始化对象的成员变量
初始化通常在变量或对象声明时发生,赋值是对象或变量已经存在时对其进行更新。
初始化是内存分配和赋初值的过程,变量或对象首次被创建时进行
初始化 int a = 10; // 直接初始化
std::vector<int> vec{1, 2, 3, 4}; // 列表初始化 列表初始化(List Initialization): 直接列表初始化与拷贝列表初始化
std::vector<int> vec = {1, 2, 3}; //这实际上是先创建一个临时对象,然后通过拷贝构造函数或移动构造函数来构造目标对象
构造函数初始化
赋值(Assignment)
赋值是指在变量或对象已经被初始化(或声明)后,将一个新的值赋给它。赋值操作通常会覆盖变量之前存储的值
变量赋值
对象赋值 // 赋值运算符重载
赋值是在变量已经被初始化或对象已经创建后,对其值进行改变,赋值不会重新分配内存,而只是将新的值赋给现有的变量或对象
语法很相似:
初始化通常在声明时进行,使用 = 操作符将初始值给变量或对象。
赋值是在变量声明和初始化之后使用 = 操作符改变其值。
元素添加
使用成员函数(如push_back)来添加元素
作用域
scope作用域和命名空间
命名空间域是指用 namespace 声明的作用域
类域是类中定义的成员变量或成员函数的作用范围
局部域 指的是在函数或代码块(如 if、for、while 等)内部定义的变量的作用范围
全局域 指的是定义在所有函数或类之外的变量。全局变量可以在整个程序中访问,任何函数或类都能使用它(除非被 static 修饰)。
:: 是作用域解析运算符,用于区分不同域中的同名变量
头文件+库文件
头文件+源文件
头文件:包含结构声明和使用这些结构的函数的原型
源代码文件:包含与结构有关的函数的代码
源代码文件:包含调用与结构相关的函数的代码
嵌套 nested
const 该变量修饰为常量,从而不可以修改
constexpr 用来修饰一些函数和变量,使其成为常量表达式
static
static 主要控制变量的存储位置和可见性,而const则控制变量的可变性 静态
static类成员变量属于类本身 静态全局变量 静态成员变量 静态成员函数
指针
指针 --是一个对象
引用
左值引用--引用不是一个对象
引用是:通过更改一个变量的引用就能达到更改该变量的效果。指针是:通过访问一个变量的地址,对其解引用后也可以更改该变量的值
对指针的引用 对常量的引用
采用传递引用的方式,因为引用只是变量的一个别名,不占用内存,所以不会发生拷贝行为。
但是引用传递有一个问题,那就是形参可以改变实参的值。
所以为了避免意外修改导致实参的值发生改,通常会采用const加上引用的方式传递参数
右值引用
在C++中,引用的符号为”&“,和取地址的符号是一样的。引用的前提是必须得有一个变量作为引用的对象
int a = 10;
int& c = a;//引用a,并且别名叫c(注意别名类型与本名类型保证一致)
别名可以通过`typedef`或`using`关键字来定义
左值引用和右值引用
左值引用和右值引用
传统的C++引用,即是左值引用
在C++中,左值引用的符号为”&“
左值有名字,能够被多个地方引用,生命周期较长
右值引用:
右值引用使用 && 符号来声明,表示对右值的引用
右值不具名,只能通过引用的方式找到 捕获右值的临时对象:右值引用可以绑定到右值,如临时对象或字面量,使我们能够操作这些临时值。
移动语义的实现通常涉及移动构造函数和移动赋值运算符。
移动构造函数: 临时对象(右值)。用到临时对象的时候就会执行移动语义
移动函数 MyClass(MyClass&& other) {} // 移动构造函 两个&操作符, 移动构造函数接收的是“右值引用”的参数。
一个右值引用(rvalue reference)创建新的对象,而无需进行深拷贝(deep copy)
移动赋值运算符 Foo& operator=(Foo&& foo)
同复制赋值运算符,唯一不同是参数为右值
右值引用的核心用途主要包括移动语义和完美转发。
std::move(),它可以将一个左值强制转换为右值引用
std::forward 是C++11引入的函数模板,它的作用是实现函数参数的完美转发,通俗的讲就是根据传入的参数,决定将参数以左值引用还是右值引用的方式进行转发
简单之处在于理解动机:C++为什么需要完美转发?
完美转发可以确保 arg 被正确转发为左值或右值,以避免额外的拷贝或不必要的移动
让模板函数能够适应各种不同的参数类型(左值、右值、const 引用等),并保持参数的原始特性
复杂之处在于理解原理:完美转发基于万能引用,引用折叠以及std::forward模板函数
左值右值在函数调用时,都转化成了左值,使得函数转调用时无法判断左值和右值。
允许程序员更加细粒度的处理对象拷贝时的内存分配问题,提高了对临时对象和不需要的对象的利用率
C++程序中,不必要的对象复制会导致额外的CPU和内存开销。使用移动语义和引用(尤其是右值引用)可以避免这种开销