过了一遍C++后记录下一些概念,以后时长温习一下
编译器负责整理当前文件进行翻译:首先查看当前文件的函数需要的参数是否定义,再查看参数的类型是否存在定义,如果参数类型是某个类,就继续查看其方法的参数是否定义,并进一步查看其参数类型是否定义。只要都符合并且通过了语法判断,C++就允许编译。
链接器负责将一批obj文件进行拼接,只要调用了某个函数就会去这批obj中找这个签名的函数的实现,如果只有一个就通过,这样将每个obj都扫一遍,直到所有调用都有唯一的函数存在,那就通过链接。如果这个签名的函数有多个,那就不通过链接。
变量本身就是一个地址存放数据而已,变量的类型没有任何意义,有意义的是占用内存的大小和解码方式,类型仅仅表示你应该在读取数据的时候一次读取多大内存而已,变量保存的数据说到底都是二进制数,用不同数据类型去解析是不同的结果,甚至大的数据类型去解析小的数据类型会获得后面的不安全的数据。
头文件本质上是拷贝一段代码并粘贴到执行单元头部,因此头文件也很容易引入链接错误(如果你的函数定义在头文件中的话),重复声明函数变量。常用的解决方式是在头文件的声明加入static,static会让引入头文件的那个执行单元仅在内部使用此函数不参与外部链接。当然针对这种链接问题现在常用方式是头文件只写声明,源码文件只写定义,这样通过导入头文件就能无缝获取需要的函数的声明通过编译,之后通过链接就可以精准定位到实现。
在C++中判断是逻辑运算,常见判断为if,else if,else。但是实际上else if是一层语法糖。本质上是一层else{if{}},因此else if会引入大量的层级嵌套,迫使CPU重复构建运算流水线,严重影响性能,因此我们应该尽量规避使用逻辑并将逻辑替换成数学运算。此外只用小于能解决就不要用小于等于这会引入更多逻辑运算。
类和结构体几乎一码事,唯一的区别是:类是默认private的结构体,结构体是默认public的类。
static关键在在class或者struct中会将此属性定位类级别的,即所有此类对象公用一个此属性。因为是类级的所以static修饰的方法也是类级的,这意味着此方法不依赖this指针,也就是可以理解为一个class库中的一个普通函数。static关键字修饰一个普通变量或者函数会将其连接器限制为内部链接,相当于一个其他语言中的local关键字。因为static修饰的数据的生命周期与程序相同,因此放在局部作用域里(即函数体或方法体里)修饰某个变量,这个变量将永远存活,且仅初始化一次,并仅限与此函数内使用,可以统计函数调用次数。
字面量就是“nihao”,这样的双引号中的数据,类型是const char[N]。存放在一个可读不可写的区域。在声明并赋值一个字面量到某种字符串类型的时候,会将字面量的值复制一份并存放在对应容器,如std::string,char*。这样就可以修改此数据了。
const修饰一个数据表示其不可修改。且针对的就是它右侧的那个。如果从const int q;表示 q这个解引用的值不可以被修改 int* cons q;表示地址不可变,const int q表示数值不可变,const修饰函数表示纯函数参数不可以修改。const修饰方法保证不修改对象状态,只负责取数和普通运算。const声明对象,会给对象加锁,限制非mutable属性的修改,以及非const函数的调用。
创建类的话,可以通过new在堆上声明(手动用delete释放);或者像原始数据类型或结构体一样声明->在栈上声明。栈上更快,但受限于作用域生命周期,以及栈比较小。堆上要手动释放,且更慢,但是有更大内存,且可以跨越生命周期和作用域。
类中的const方法中不允许任何参数进行修改,因此在此方法里所有使用this指针的场景,this都会出于不可修改值和指向的状态。
在进去一个作用域既一对大括号,并离开一个作用域的过程中,在作用域里面声明在栈上的变量和对象都会在离开时自动丢失,除非用new声明在堆里,但是堆里的数据需要手动删除。
当你使用赋值符号将一个变量赋给另一个变量时,你总是在复制数据并赋值(除引用),在将同类对象用赋值运算符进行声明且赋值时候就会调用拷贝构造函数。拷贝构造函数由程序员实现,当然c++也有默认拷贝构造函数,但是浅拷贝,这意味着只会将指针进行拷贝而不拷贝里面数据,深拷贝方案需手动实现(拷贝函数,即:入参和类型一样的构造函数)。拷贝构造函数可以被删除(既没有默认的),unique_ptr就是这样实现的。
在栈上分配内存就是一条cpu指令很快。堆上分配,需要调用new操作符,new调用了malloc,malloc需要取查gdt表,检查空闲内存,获取内存地址,记录到gdt表,在用完后还得delete。
编译的初期会处理#include文件这个时候叫与编译器,此时会进行处理#include相关的宏(就是将代码替换为其他东西),它和模板很相似,但是模板的处理会更晚一些。宏本质是让代码交给编译器前先让预处理器修改代码。宏的优势在于可以实现调试代码在调试版,发行版不包含调试代码。
lambda表达式的组成由捕获,入参表,函数体组成。lambda是一个封闭的环境,从外界获取数据主要走入参和捕获,如果lambda作为一个参数在另一个函数调用,那入参的作用域就会受限于这个调用它的函数,如果想要获取主函数的数据控制就依赖捕获了。
在C++中,联合体和结构体相似,它相当于指定一块内存存放一个数据,只不过这个数据可以有几种方式来解读,每种方式解读有不同含义,但是如果修改任意一种解读方式的数据,本质上都是在修改这快内存上的数据,当然C++不安全不保障不兼容的数据不会出现乱码情况。这点rust的enum做得很好,rust在编译时有检查,C++没有。在结构体里嵌套union相当于说,指定一批数据,有多种解读方式,一个四维vector有四个轴,也可以被当成两个二维vector解读。
在涉及继承时一定需要使用虚析构函数,否者无法保证内存安全,因为如果继承的只是析构函数,那就会先执行父类构造,再子类构造,再父类析构(父类析构不执行,增加内存泄漏风险)。用虚析构函数提供继承,子类先执行父类构造,再子类构造,再子类析构,再父类析构。执行类似于栈,总之是合理析构了子类的属性。
dynamic_cast常用于类型转换,如果转换失败就会返回一个Null指针,也就是0,因此也常用于类型验证。dynamic_cast有一层安全的保障,会确定是否可以类型转换,但也带来了性能开销。而隐式类型转换不支持父类转子类,显示强制类型转换又没有安全保障,可能让不兼容的类转换了(访问没有的东西会crash),static_cast在编译时有一些检查,但是不够严格,能排除一些错误,但不能像dynamic_cast一样保证不crash,但性能也要好一些。
C++17特性Optional容器包裹的数据可以根据存放的数据是否为null决定是否返回.value_or()指定的其他数据。
c++17特性Varient容器,单一变量存放多种类型数据,允许用一个变量存放不通的数据类型(并且不同于联合体内存里必须是相同机器码),感觉比union更类似于rust的enum,且它的内存占用是所有可能性的大小的和,这一点上更像结构体。
C++17特性std的Any容器,功能类似于Varient,但是any这个词让它不够安全,并且any动态分配内存会损失性能,似乎是用来搞笑用的,没啥用途。
C++17新特性提供string_view类直接读取内存中存在的数据的片段而不是申请新的内存,substr()就会申请内存。
C++单例设计:通过删除一个类的拷贝构造方法,赋值构造方法,将构造方法私有化,通过static修饰(static函数可以通过类名调用,static变量只能初始化一次)。这样类可掉的getinstance接口会通过私有构造方法返回一个static实例,即全局唯一的实例。
左值和右值: 左值就是有内存地址的对象,可以用&运算符取址,右值是字面值或者函数的返回值,实际就是字面值const char* 字符串是特殊的右值。右值就是一个存粹的值不存放在任何意义上的堆栈中,左值引用只能引用左值,右值引用只能引用右值,const修饰的左值可以引用左值或者右值。
在C++17之前参数的传入并非按照既定的顺序从左往右依次计算完成并放入函数,编译器实际上允许并行计算函数所用的参数,因此这就带来了不可预测性。但是C++17(MSVC和Gcc)传入的参数会从右往左依次计算好并传入函数,C++17(Clang)则顺序相反。总之就是这种行为是未定义的,各家编译器都不相同,但是C++17确实是让参数传递时不在并行计算出来了。
移动赋值操作符一般在类中重载,以方便将数据移动而非拷贝。std::move将一个对象转换成临时对象。
C++三法则:如果需要析构函数,则一定需要拷贝构造函数和拷贝赋值操作符;C++五法则:为了支持移动语义,有增加了移动构造函数和移动赋值运算符。
posted @
2025-09-30 20:08
抓泥鳅的小老虎
阅读(
7 )
评论()
收藏
举报