C/C++复习,关于底层实现

4 篇文章 0 订阅

构造函数

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

默认构造函数

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

构造函数初始化列表

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

new & delete

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

this

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

生命周期和作用域

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

链接

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

C++新增内联函数

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

.c .cpp

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

const成员函数

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

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

const 对象

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

类提前声明 正式声明

  1. 这里简单介绍一下类的提前声明。一般情况下,类必须在正式声明之后才能使用;但是某些情况下(如上例所示),只要做好提前声明,也可以先使用。
  2. 类的提前声明的使用范围是有限的,只有在正式声明一个类以后才能用它去创建对象。
#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

转载
转载

左值右值个人理解

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

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

  1. 左值语义 空间
  2. 右值语义 值

析构函数

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

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

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

虚函数

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

重载 遮蔽 覆盖

转载

纯虚函数

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

函数模板

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

类型实参 函数实参

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

模板实例化

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

.bss

  1. .bss存放程序中为初始化的和零值全局变量。静态分配,在程序开始时通常会被清零。text和data段都在可执行文件中,由系统从可执行文件中加载;而bss段不在可执行文件中,由系统初始化
  2. bss段只占运行时的内存空间而不占文件空间。在程序运行的整个周期内,.bss段的数据一直存在
  3. .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
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值