C++ 基础随笔

1. C/C++内存的几种类型

存储对象 初始值 分配
程序运行时动态分配的对象,例如new的对象 随机 ⼿动分配和回(new、malloc、delete、free) ,空间较⼤,易内存泄漏和产生空闲碎片
局部变量、函数参数 随机 编译器管理分配和回收
代码区 二进制代码
全局/静态存储区 全局变量、static变量 缺省值 分为初始化和未初始化两个相邻区域,存储初始化和未初始化的全局变量和静态变量
常量存储区 常量 ⼀般不允许修改
自由存储区 C++关于(new)的一种说法

2. 堆和栈的区别

存储对象 初始值 分配 空间 地址扩展 存储对象的生存周期
程序运行时动态分配的对象,例如new的对象 随机 ⼿动分配和回(new、malloc、delete、free) ,空间较⼤,易内存泄漏和产生空闲碎片 不连续 低地址向高地址扩展、空间大 由程序控制
局部变量、函数参数 随机 编译器管理分配和回收 连续 高地址向低地址扩展、空间小 在其定义的程序块运行时存在

3.程序编译的过程

⼀段⾼级语⾔代码经过四个阶段的处理形成可执⾏的⽬标⼆进制代码。(main.c)→预处理器(main.i)编译器(main.s)汇编器(main.o)链接器(main).

阶段 生成文件 过程
hello.c、main.c 源码
预处理 hello.i、main.i 读取程序源码的文本内容,按照预处理指令 (例如头文件包含、宏定义等) 替换 相应的文本内容。预处理过程的输出文件仍然是文本文件 ,但是文件体积比输入文件大
编译器 hello.s、main.s 将预处理后的程序文本 (*.i) 转换成汇编代码 (Assembly Code / *.s)
汇编器 hello.o、main.o 将汇编代码 (.s) 转换成与指定硬件相匹配的机器码 (Machine Code) 。汇编过程的输出文件被称为目标文件 (.o) ,这是一种是二进制文件。对于每一个源码文件 (*.c) ,汇编后将产生相应的目标文件
链接器 hello、main 将项目中的多个目标文以及所需的库文件链接成最终的可执行文件

4.C和C++的区别

C 和 C++ 在基本语句上没有过⼤的区别。

C++ 有新增的语法和关键字,语法的区别有头⽂件的不同和命名空间的不同, C++ 允许我们⾃⼰定义⾃⼰的空间,C 中不可以。关键字⽅⾯⽐如 C++ 与 C 动态管理内存的⽅式不同, C++ 中在 malloc 和 free 的基础上增加了 new和 delete,⽽且 C++ 中在指针的基础上增加了引⽤的概念,关键字例如 C++中还增加了 auto, explicit 体现显示和隐式转换上的概念要求,还有 dynamic_cast 增加类型安全⽅⾯的内容。

函数⽅⾯ C++ 中有重载和虚函数的概念: C++ ⽀持函数重载⽽ C 不⽀持,是因为 C++ 函数的名字修饰与 C 不同,C++ 函数名字的修饰会将参数加在后⾯,例如, int func(int,double)经过名字修饰之后会变成_func_int_double,⽽ C 中则会变成 _func,所以 C++ 中会⽀持不同参数调⽤不同函数。

C++ 还有虚函数概念,⽤以实现多态。

C 的 struct 和 C++ 的类也有很⼤不同: C++ 中的 struct 不仅可以有成员变量还可以成员函数,⽽且对于struct 增加了权限访问的概念, struct 的默认成员访问权限和默认继承权限都是 public, C++ 中除了 struct 还有class 表示类, struct 和 class 还有⼀点不同在于 class 的默认成员访问权限和默认继承权限都是 private。

C++ 中增加了模板,提供了更加强⼤的 STL 标准库。

C 是⼀种结构化的语⾔,重点在于算法和数据结构。 C 程序的设计⾸先考虑的是如何通过⼀个代码,⼀个过程对输⼊进⾏运算处理输出。⽽ C++ ⾸先考虑的是如何构造⼀个对象模型,让这个模型能够契合与之对应的问题领域,这样就能通过获取对象的状态信息得到输出。

5. 常量在内存中的位置

类型 位置
局部常量 栈区
全局常量 编译期⼀般不分配内存,放在符号表中以提⾼访问效率
字⾯值常量 常量区、⽐如字符串

6. C++ 中重载和重写,重定义的区别

区别
重载 同⼀访问区内被声明的⼏个具有不同参数列表(参数类型\个数\顺序不同)的同名函数;与返回类型无关
重写 派⽣类中重新定义⽗类中除了函数体外完全相同虚函数 ,static函数不能被重写 。父类中private的virtuan虚函数在派生类中可以改为public或其他
重定义 派⽣类重新定义⽗类中相同名字⾮ virtual 函数参数列表和返回类型都可以不同,即⽗类中除了定义成 virtual 且完全相同的同名函数才不会被派⽣类中的同名函数所隐藏(重定义)。

7. C++ 中的构造函数

类的对象被创建时,编译系统为对象分配内存空间,并⾃动调⽤构造函数,由构造函数完成成员的初始化⼯作。即构造函数的作⽤:初始化对象的数据成员。

构造函数 作用
⽆参数构造函数(默认构造函数) 如果没有明确写出⽆参数构造函数,编译器会⾃动⽣成默认的⽆参数构造函数,函数为空,什么也不做,如果不想使⽤⾃动⽣成的⽆参构造函数,必需要⾃⼰显示写出⼀个⽆参构造函数。
一般构造函数(重载构造函数) ⼀般构造函数可以有各种参数形式,⼀个类可以有多个⼀般构造函数,前提是参数的个数或者类型不同,创建对象时根据传⼊参数不同调⽤不同的构造函数
拷贝数构造函数 拷⻉构造函数的函数参数为对象本身的引⽤,⽤于根据⼀个已存在的对象复制出⼀个新的该类的对象,⼀般在函数中会将已存在的对象的数据成员的值⼀⼀复制到新创建的对象中。如果没有显示的写拷⻉构造函数,则系统会默认创建⼀个拷⻉构造函数,但当类中有指针成员时,最好不要使⽤编译器提供的默认的拷⻉构造函数,最好⾃⼰定义并且在函数中执⾏深拷⻉
类型转换构造函数 根据⼀个指定类型的对象创建⼀个本类的对象,也可以算是⼀般构造函数的⼀种,这⾥提出来,是想说有的时候不允许默认转换的话,要记得将其声明为 explict 的,来阻⽌⼀些隐式转换的发⽣。
赋值运算符的重载 不属于构造函数 ,类似于构造函数 ,将=右边的本类对象的值复制给=左边的对象,=左右两边的对象必需已经被创建。如果没有显示的写赋值运算符的重载,系统也会⽣成默认的赋值运算符,做⼀些基本的拷⻉⼯作。

8.C++ 中的四种智能指针

作用:智能指针其作⽤是管理指针,避免申请的空间在函数结束时忘记释放,造成内存泄漏。
原理:智能指针实质是一个对象,生命周期结束,自动析构释放资源。

智能指针 作用 示例
auto_ptr C++98 的⽅案, C11 已抛弃,采⽤所有权模式 auto_ptr p1 (new string ("hello")); auto_ptr p2; p2 = p1; //auto_ptr 不会报错.
unique_ptr 替换 auto_ptr ,实现独占式拥有或严格拥有概念,保证同⼀时间内只有⼀个智能指针可以指向该对象。它对于避免资源泄露特别有⽤。采⽤所有权模式 unique_ptr p3 (new string (auto));unique_ptr p4;p4 = p3;//此时会报错,编译器认为 p4=p3 ⾮法,避免了 p3 不再指向有效数据的问题
shared_ptr 共享型,强引⽤ ,多个智能指针可以指向相同对象,每多一个指向相同对象的shared_ptr,引用计数加一,每析构一次,引用计数减一,计数为0时释放资源。构造方法:new、或者传⼊auto_ptr,unique_ptr,weak_ptr 来构造,use_count() 查看资源的所有者个数,release() 释放资源所有权,计数减⼀,计数等于 0时,资源会被释放。
weak_ptr(弱引⽤) 1. weak_ptr 是⼀种不控制对象⽣命周期的智能指针,它指向⼀个 shared_ptr 管理的对象。进⾏该对象的内存管理的是那个强引⽤的 shared_ptr。
2.weak_ptr 只是提供了对管理对象的⼀个访问⼿段。 weak_ptr 设计的⽬的是为配合 shared_ptr ⽽引⼊的⼀种智能指针来协助 shared_ptr ⼯作,它只可以从⼀个 shared_ptr 或另⼀个 weak_ptr 对象构造,,它的构造和析构不会引起引⽤记数的增加或减少.
3.weak_ptr 是⽤来解决 shared_ptr 相互引⽤时的死锁问题,如果说两个 shared_ptr 相互引⽤,那么这两个指针的引⽤计数永远不可能下降为0,也就是资源永远不会释放。它是对对象的⼀种弱引⽤,不会增加对象的引⽤计数,和 shared_ptr 之间可以相互转化, shared_ptr 可以直接赋值给它,它可以通过调⽤ lock 函数来获得shared_ptr。
4. 当两个智能指针都是 shared_ptr 类型的时候,析构时两个资源引⽤计数会减⼀,但是两者引⽤计数还是为 1,致跳出函数时资源没有被释放(的析构函数没有被调⽤),解决办法:把其中⼀个改为weak_ptr就可以。

9. 野(wild)指针与悬空(dangling)指针有什么区别?如何避免?

野指针(wild pointer): 就是没有被初始化过的指针。⽤ gcc -Wall 编译, 会出现 used uninitialized 警告。
悬空指针: 是指针最初指向的内存已经被释放了的⼀种指针。
⽆论是野指针还是悬空指针,都是指向⽆效内存区域(这⾥的⽆效指的是"不安全不可控")的指针。 访问"不安全可控"(invalid)的内存区域将导致"Undefined Behavior"。
如何避免使⽤野指针? 在平时的编码中,养成在定义指针后且在使⽤之前完成初始化的习惯或者使⽤智能指针。

10. C++ 中的指针参数传递和引⽤参数传递

指针参数传递: 本质上是值传递(地址值)。值传递过程中,被调函数的形式参数作为被调函数的局部变量处理,会在栈中开辟内存空间以存放由主调函数传递进来的实参值,从⽽形成了实参的⼀个副本(替身)。值传递的特点是,被调函数对形式参数的任何操作都是作为局部变量进⾏的,不会影响主调函数的实参变量的值(形参指针变了,实参指针不会变)。

int a = 10;
int* p;
p = &a;  // p == 0x70fd60;  *p == 10
test(p); // p == 0x70fd60;  *p == 0  

void test(int* ptr)
{
	*ptr = 0;   // ptr == 0x70fd60;*ptr == 0;   p == 0x70fd60;*p == 0;  ptr此时地址值与p一致,修改地址指向值,
	int num = 5;
	ptr = # // ptr == 0x70fcf4; *ptr == 5;  p == 0x70fd60;*p == 0; ptr指向地址变化; p地址值不受影响,p地址指向值不变化
}

引⽤参数传递: 被调函数的形式参数也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参(本体)的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量(根据别名找到主调函数中的本体)。因此,被调函数对形参的任何操作都会影响主调函数中的实参变量。

引⽤传递和指针传递是不同的,虽然他们都是在被调函数栈空间上的⼀个局部变量,但是任何对于引⽤参数的处理
都会通过⼀个间接寻址的⽅式操作到主调函数中的相关变量。⽽对于指针传递的参数,如果改变被调函数中的指针
地址,它将应⽤不到主调函数的相关变量。如果想通过指针参数传递来改变主调函数中的相关变量(地址),那就
得使⽤指向指针的指针或者指针引⽤。
从编译的⻆度来讲,程序在编译时分别将指针和引⽤添加到符号表上,符号表中记录的是变量名及变量所对应地
址。指针变量在符号表上对应的地址值为指针变量的地址值,⽽引⽤在符号表上对应的地址值为引⽤对象的地址值
(与实参名字不同,地址相同)。符号表⽣成之后就不会再改,因此指针可以改变其指向的对象(指针变量中的值
可以改),⽽引⽤对象则不能修改。

你可能感兴趣的:(C++,c++)