CC++复习,关于底层实现

it2024-01-06  68

目录

构造函数默认构造函数构造函数初始化列表new & deletethis生命周期和作用域链接C++新增内联函数.c .cppconst成员函数const 对象类提前声明 正式声明C++返回值优化 RVO左值右值个人理解析构函数虚函数重载 遮蔽 覆盖纯虚函数函数模板类型实参 函数实参模板实例化.bss

构造函数

在C++中,有一种特殊的成员函数,它的名字和类名相同,没有返回值,不需要用户显式调用(用户也不能调用),而是在创建对象时自动执行。这种特殊的成员函数就是构造函数(Constructor)。构造函数的调用是强制性的,一旦在类中定义了构造函数,那么创建对象时就一定要调用,不调用是错误的。如果有多个重载的构造函数,那么创建对象时提供的实参必须和其中的一个构造函数匹配;反过来说,创建对象时只有一个构造函数会被调用。

默认构造函数

如果用户自己没有定义构造函数,那么编译器会自动生成一个默认的构造函数,只是这个构造函数的函数体是空的,也没有形参,也不执行任何操作。一个类必须有构造函数,要么用户自己定义,要么编译器自动生成。一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成。最后需要注意的一点是,调用没有参数的构造函数也可以省略括号。对于示例2的代码,在栈上创建对象可以写作Student stu()或Student stu,在堆上创建对象可以写作Student *pstu = new Student()或Student *pstu = new Student,它们都会调用构造函数 Student()。

构造函数初始化列表

成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关。使用初始化列表少了一次调用默认构造函数的过程必须使用初始化列表的情况 常量成员,因为常量只能初始化不能赋值,所以必须放在初始化列表里面引用类型,引用必须在定义的时候初始化,并且不能重新赋值,所以也要写在初始化列表里面没有默认构造函数的类类型,因为使用初始化列表可以不必调用默认构造函数来初始化,而是直接调用拷贝构造函数初始化 从概念上来讲,构造函数的执行可以分成两个阶段,初始化阶段和计算阶段,初始化阶段先于计算阶段. 初始化阶段 所有类类型(class type)的成员都会在初始化阶段初始化,即使该成员没有出现在构造函数的初始化列表中.计算阶段 一般用于执行构造函数体内的赋值操作。

new & delete

C++ 中的 new 和 delete 分别用来分配和释放内存,它们与C语言中 malloc()、free() 最大的一个不同之处在于:用 new 分配内存时会调用构造函数,用 delete 释放内存时会调用析构函数。

this

this 实际上是成员函数的一个形参,在调用成员函数时将对象的地址作为实参传递给 this。不过 this 这个形参是隐式的,它并不出现在代码中,而是在编译阶段由编译器默默地将它添加到参数列表中。this 作为隐式形参,本质上是成员函数的局部变量,所以只能用在成员函数的内部,并且只有在通过对象调用成员函数时才给 this 赋值。成员函数最终被编译成与对象无关的普通函数,除了成员变量,会丢失所有信息,所以编译时要在成员函数中添加一个额外的参数,把当前对象的首地址传入,以此来关联成员函数和成员变量。这个额外的参数,实际上就是 this,它是成员函数和成员变量关联的桥梁。 转载 C++函数编译原理和成员函数的实现

生命周期和作用域

在函数内定义的变量是局部变量,而在函数之外定义的变量则称为外部变量,外部变量也就是我们所讲的全局变量。它的存储方式为静态存储,其生存周期为整个程序的生存周期。全局变量可以为本文件中的其他函数所共用,它的有效范围为从定义变量的位置开始到本源文件结束。然而,如果全局变量不在文件的开头定义,有效的作用范围将只限于其定义处到文件结束。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字 extern 对该变量作“外部变量声明”,表示该变量是一个已经定义的外部变量。有了此声明,就可以从“声明”处起,合法地使用该外部变量。 转载 extern关键字,C语言extern关键字用法详解

链接

在程序运行之前确定符号地址的过程叫做静态链接(Static Linking);如果需要等到程序运行期间再确定符号地址,就叫做动态链接(Dynamic Linking)。

C++新增内联函数

要在函数定义处添加 inline 关键字

.c .cpp

对于.c文件,gcc编译后的func的.type为func;而对于.cpp文件gcc编译后的func的.type为_Z4funcv, 则编译器会根据文件后缀名对函数或变量名对某些修正,一个是C的编译方式,一个是C++的编译方式。g++无论是对.c文件还是.cpp文件都是按C++的方式编译的,这是和gcc是有区别的。gcc会根据文件后缀名来确定编译方式,而g++只有C++的编译方式。

const成员函数

const 成员函数可以使用类中的所有成员变量,但是不能修改它们的值常成员函数需要在声明和定义的时候在函数头部的结尾加上 const 关键字最后再来区分一下 const 的位置:

函数开头的 const 用来修饰函数的返回值,表示返回值是 const 类型,也就是不能被修改,例如const char * getname()。 函数头部的结尾加上 const 表示常成员函数,这种函数只能读取成员变量的值,而不能修改成员变量的值,例如char * getname() const。

const 对象

一旦将对象定义为常对象之后,就只能调用类的 const 成员(包括 const 成员变量和 const 成员函数)了。

类提前声明 正式声明

这里简单介绍一下类的提前声明。一般情况下,类必须在正式声明之后才能使用;但是某些情况下(如上例所示),只要做好提前声明,也可以先使用。类的提前声明的使用范围是有限的,只有在正式声明一个类以后才能用它去创建对象。 #include <cstdio> using namespace std; class A; class B { public: void fun(A*); }; class A { int i; friend void B::fun(A*); }; void B::fun(A* p) { printf("%d\n",p->i); } int main(){ A a; B b; b.fun(&a); return 0; }

C++返回值优化 RVO

转载 转载

左值右值个人理解

左值 值存储在内存中的表达式,可取左值语义和右值语义右值 值存储在寄存器中的表达式,只可取右值语义临时数据是右值,临时变量是左值右值转左值条件:临时数据存储为临时变量

当寄存器存储不下对象时,临时数据存储到内存中,产生临时对象 常引用指向时,产生临时变量

左值语义 空间右值语义 值

析构函数

和构造函数类似,析构函数也不能被继承。与构造函数不同的是,在派生类的析构函数中不用显式地调用基类的析构函数,因为每个类只有一个析构函数,编译器知道如何选择,无需程序员干涉。另外析构函数的执行顺序和构造函数的执行顺序也刚好相反:

创建派生类对象时,构造函数的执行顺序和继承顺序相同,即先执行基类构造函数,再执行派生类构造函数。 而销毁派生类对象时,析构函数的执行顺序和继承顺序相反,即先执行派生类析构函数,再执行基类析构函数。

在实际开发中,一旦我们自己定义了析构函数,就是希望在对象销毁时用它来进行清理工作,比如释放内存、关闭文件等,如果这个类又是一个基类,那么我们就必须将该析构函数声明为虚函数,否则就有内存泄露的风险。也就是说,大部分情况下都应该将基类的析构函数声明为虚函数。

虚函数

过指针访问非虚函数时,编译器会根据指针的类型来确定要调用的函数;也就是说,指针指向哪个类就调用哪个类的函数为了方便,你可以只将基类中的函数声明为虚函数,这样所有派生类中具有遮蔽关系的同名函数都将自动成为虚函数。

重载 遮蔽 覆盖

转载

纯虚函数

约束派生类的功能基类即使不实现某些虚函数,基类指针也可以调用派生类对应虚函数

函数模板

在C++中,数据的类型也可以通过参数来传递,在函数定义时可以不指明具体的数据类型,当发生函数调用时,编译器可以根据传入的实参自动推断数据类型。这就是类型的参数化。值(Value)和类型(Type)是数据的两个主要特征,它们在C++中都可以被参数化。

类型实参 函数实参

在使用类模板创建对象时,程序员需要显式的指明实参(也就是具体的类型)。例如对于下面的 Point 类:通过函数实参来确定模板实参(也就是类型参数的具体类型)的过程称为模板实参推断。类型实参只有类型函数实参有类型和值

模板实例化

模板的实例化是由编译器完成的,而不是由链接器完成的,这可能会导致在链接期间找不到对应的实例。

.bss

.bss存放程序中为初始化的和零值全局变量。静态分配,在程序开始时通常会被清零。text和data段都在可执行文件中,由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化。bss段只占运行时的内存空间而不占文件空间。在程序运行的整个周期内,.bss段的数据一直存在.data和.bss段的区别可以通过下面程序验证:#include <stdio.h> char global_arr[1024 * 1024]; //存放在.bss段 int main(void) { return 0; } 显然,global_arr数组占据的1M空间并没有占据文件空间。将global_arr数组改放在.data段中:char global_arr[1024 * 1024] = {4}; //存放在.data段 文件变成了1M多,显然.data段上的数据是占据文件空间的。
最新回复(0)