C++笔记—基础语法(1)
main 执行之前和之后
执行之前:
- 设置栈指针
- 初始化static 和global变量
- 将未初始化的global变量赋初始值short/int/long=0,bool=false,pointer=null
- 全局对象初始化,调用构造函数
- 将main的argc/argv传给main
- attribute((constructor))
main执行之后:
- 全局对象的析构函数
- atexit函数
- attribute((destructor))
指针 Pointer和引用 Reference
指针可以在声明时不初始化,可以稍后赋值,并且可以重新赋值,指向不同的对象。引用必须在声明时初始化,一旦初始化后就不能改变引用的对象。
指针可以为空(nullptr),表示不指向任何对象。引用不能为空,必须始终引用一个有效对象。
指针可以有多级,如指向指针的指针。引用只能有一级。
指针可以进行算术运算,如++或—,引用不能进行算术运算。
指针本身占用一定的内存空间(通常是4bytes(32 bit)或8bytes(64 bit));引用不占用额外的内存空间。
指针是一个变量,存储的地址,而引用等同于原变量,可以看作是原变量的别名,
指针和引用使用时机
引用:
- 希望在函数内修改原始对象时
- 在重载操作符时使用引用
1
bool operator==(const MyClass& other) const;
- 对于大型对象,使用const引用可以避免不必要的复制。
- 对栈空间敏感(递归)时
指针:
- 当对象的生命周期需要超出创建它的函数作用域时,返回函数内部动态分配的内存(使用指针)
1
2
3
4std::vector<int>* createAndFillVector(int size) {
auto* vec = new std::vector<int>(size);
return vec;
} - 大对象创建,不想在栈上分配内存
- 使用多态
1
2
3
4
5
6
7
8
9class Animal { public: virtual void speak() = 0; };
class Dog : public Animal { public: void speak() override { /* ... */ } };
class Cat : public Animal { public: void speak() override { /* ... */ } };
Animal* createAnimal(std::string type) {
if (type == "dog") return new Dog();
if (type == "cat") return new Cat();
return nullptr;
}
堆和栈的区别
堆(Heap)和栈(Stack)是C++中两种主要的内存分配区域
内存分配方式:
- 栈:自动分配和释放。当进入一个函数时,栈上会自动分配内存;当函数退出时,这些内存会自动释放。
- 堆:需要手动分配和释放。使用 new 运算符分配内存,使用 delete 运算符释放内存。
内存大小:
- 栈:大小通常较小,且固定。在大多数系统中,栈的大小在编译时就确定了。
- 堆:大小通常较大,且可以动态增长(受限于可用的物理内存)。
分配速度:
- 栈:分配非常快,只需要移动栈顶指针。
- 堆:分配相对较慢,需要在空闲列表中查找合适的内存块。
内存布局和生长:
- 栈:内存连续,高地址向低地址增长。不容易有碎片
- 堆:内存可能不连续,低地址向高地址增长。容易有碎片
生命周期:
- 栈:局部变量的生命周期与函数调用周期一致。
- 堆:对象的生命周期由程序员控制,可以持续到整个程序结束。
管理方式:
- 栈:由编译器自动管理。
- 堆:由程序员手动管理,或使用智能指针等工具辅助管理。(容易memory leak)
数据访问速度:
- 栈:访问速度较快,因为内存连续且使用相对寻址。
- 堆:访问速度相对较慢,因为可能需要解引用指针。
常见用途:
- 栈:存储局部变量、函数参数、返回地址等。
- 堆:存储动态分配的对象,如需要在函数调用之间持续存在的数据。
1 |
|
内存管理中栈的先进后出
压栈(Push)操作:
- 当有新数据需要压入栈时,处理器会先将栈指针减小(因为栈向低地址增长)。
然后将数据存储在新的栈顶位置。
出栈(Pop)操作:
- 当需要弹出数据时,处理器会读取栈指针指向的数据。
然后增加栈指针,使其指向下一个元素。
1 |
|
当
main()
调用funcA()
时:funcA
的返回地址被压入栈。- 为
a
分配栈空间。
当
funcA()
调用funcB(a)
时:funcB
的返回地址被压入栈。- 参数
a
的值被压入栈。 - 为
y
分配栈空间。
当
funcB()
返回时:y
的栈空间被释放。- 参数
a
的值从栈中移除。 - 处理器使用返回地址跳回
funcA
。
当
funcA()
返回时:a
的栈空间被释放。- 处理器使用返回地址跳回
main
。
int p[10] & int (p)[10]
- int *p[10]
- 它是一个数组(p是一个数组名)
- 数组的大小是10
- 数组的每个元素是一个指向int的指针
- int (*p)[10]
- 它是一个指针(p是一个指针)
- 这个指针指向一个数组
- 被指向的数组包含10个int元素
new/delete 和 malloc/free
类型安全:
- new/delete 是类型安全的,malloc/free 不是。
- new 返回类型化的指针,malloc 返回 void*。
构造和析构:
- new 会调用构造函数,delete 会调用析构函数。
- malloc/free 不涉及构造和析构。
- 重载:
- 可以重载 new 和 delete 操作符。
- 可以覆盖 malloc 和 free。
对于非基本类型的对象时,创建要使用构造constructor函数,销毁使用析构destructor函数
Variable Declaration & Definition 声明和定义
- 变量声明:
- 声明告诉编译器变量的名称和类型。
- 不分配内存空间。
- 通常用于头文件中,告诉其他文件该变量的存在。
- 可以多次声明同一个变量。
- 变量定义:
- 定义不仅声明了变量,还为其分配了内存空间。
- 初始化变量(可选)。
- 只能定义一次。
- 通常在源文件(.cpp)中进行。
struct和class区别
默认访问权限
- struct: 默认为public
- class: 默认为private
默认继承方式
- struct: 默认public继承
- class: 默认private继承
宏定义macro definition
基本概念:
- 宏定义使用 #define 指令
- 在编译之前由预处理器处理
- 本质上是简单的文本替换
语法:
1
宏定义的类型:
a. 对象式宏(Object-like Macros):
1
b. 函数式宏(Function-like Macros):
1
- 函数有类型检查
- 函数有返回值
宏定义和typedef/const区别
- 宏定义在预处理阶段处理, typedef/const在编译阶段处理
- 宏定义没有类型检查,typedef/const有类型检查
- 宏定义是简单的文本替换,而 typedef 和 using 创建了真正的类型别名。
1
2
3
4
5
6
7
8
9
10
11// 使用宏定义
// 使用typedef
typedef std::vector<int> IntVector;
// 使用 C++11 的 using (类似于 typedef)
using IntVec = std::vector<int>;
// 使用 const
const double pi = 3.14159;
const 和static
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议。转载请注明来自 Mio's blog!