前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >C++关键知识点梳理

C++关键知识点梳理

原创
作者头像
sansan
修改2023-03-14 17:29:08
9240
修改2023-03-14 17:29:08
举报
文章被收录于专栏:屌丝程序媛屌丝程序媛

基本类型

基本类型的大小随编译器决定,下面以32位为例

类型

大小 (字节)

bool

未定义

char

1

short

2

int

4

long

4

long long

8

float

4

double

8

变量

  • 变量初始化,在C++中,使用未初始化变量是一种错误的编程行为,未初始化变量含有一个不确定的值,所以在定义变量时最好初始化,类成员使用初始化列表在构造函数中初始化均是良好的编程习惯;
  • 变量的定义和声明:变量可以在多个文件中声明(external int i),但是只能在一个文件中被定义(int j);

复合类型

  • 引用:引用即别名,引用为对象起了另一个名字,所以引用必须初始化。引用只能绑定在对象上,而不能与字面量或者某个表达式的计算结果绑定在一起;const 指针 & 引用函数
  • 指针:指向另一类型的对象,是对象不是别名,所以不需要定义时初始化,但是未经初始化的指针容易引发运行时错误,所以建议指针定义时使用nullptr初始化。

左值引用&右值引用

  • 左值引用:常规引用,可支持取地址运算符&获取内存地址;
  • 右值引用:右值是临时对象、字面量等表达式,右值引用解决临时对象或函数返回值给左值对象时的深度拷贝;
  • std::move:将输入的左值或右值转换为右值引用类型的临终值;避免额外的深度拷贝;
代码语言:javascript
复制
template<class T>
typename std::remove_reference<T>::type&& move(T&& a) noexcept
{ 
  return static_cast<typename std::remove_reference<T>::type&&>(a);
}
  • std::forward:如果函数forward的实参的数据类型是左值引用,则返回类型为左值引用;如果函数forward的实参的数据类型是右值引用,则返回类型为右值引用,返回值的分类属于临终值,从而把参数的信息完整地传递给下一级被调用的函数
代码语言:javascript
复制
template< class T > T&& forward( typename std::remove_reference<T>::type& t )
{
  return static_cast<T&&>(t);
}
template< class T > T&& forward( typename std::remove_reference<T>::type&& t )
{
  return static_cast<T&&>(t);
}

const

  • 定义:不被改变的值使用const修饰;
  • 使用:const在定义时被初始化(例如 const int bufSize = 512;),多个文件引用const对象时使用external声明,例如其他文件需要使用已经定义的bufSize,需在使用的cc文件中声明external const int budSize;
  • const &:C++中const的引用经常用于函数的参数或者函数的返回值,用于不被修改的对象使用安全性,例如,void print(const std::vector<int>& data); 非常量指针不能指向常量对象const double pi = 3.14 double *ptr = π //❌,非常量指针不能指向常量对象
  • 顶层指针:指针本身是常量,不能指向其他对象,例如 int *const p1=&i,const int ci =42;
  • 底层指针:指针所指向的对象是常量,const int *p2=&ci;
  • 常量在类中使用:对于不修改类成员变量的成员函数,例如,get操作的成员函数最好定义为常量成员函数;常量对象、常量对象的引用或指针都只能调用常量成员函数。
代码语言:javascript
复制
double Sales_data::avg_price() const {
    if (unit_sold) {
        return revenue/units_sold;
    } else {
        return 0;
    }
}

explicit

防止函数隐式转换,在单参数的构造函数声明时用得较多。

类似于函数,但是其()中的参数不是真的函数参数,在编译器进行宏展开时对()里的参数进行"一对一"的替换。

初始化列表

类的常量和引用成员必须在初始化列表中初始化,因为这两类成员不支持赋值操作,对象通过初始化列表初始化避免调用对象的默认构造函数进行初始化,因此效率更高。

代码结构

  1. 宏:一系列预定义规则,替换指定的文本模式,例如define PI 3.14,预处理时,对宏定义替换展开;
  2. 块:{}括号内由0条或多条语句组成的序列;
  3. 函数:函数是一个包含函数名、函数体、函数返回类型、函数参数(可有可无)的代码块,函数可以通过参数类型或数量不同实现函数重载,编译器从一组重载函数中选取最佳函数匹配。内联函数(inline)适用于逻辑简单的小代码块,编译器引用内联函数的地方展开,节约了函数调用的开销。
  4. 结构体:定义了一组关联的数据结构,C++中结构体不具备继承、封装、动态面向对象的特点,成员默认访问权限是public,有构造函数和析构函数。例如:
代码语言:javascript
复制
// 结构体
struct Car {
    string type,
    int tire_nums
}

5.类:类的基本思想是数据抽象和封装,抽象依赖于接口和动态实现的分离的编程。类是C++实现面向对象编程的三大特征:继承、封装、动态的方式之一;类具备构造函数和析构函数。

  • 构造函数/默认构造函数:控制对象的初始化过程,成员的初始化顺序与它们在类定义中出现的顺序一致。默认构造函数没有参数,在没有定义任何构造函数的情况下,编译器会帮我们自动定义默认构造函数,否则我们定义了其他构造函数后,一定要显示定义默认构造函数;
  • 析构函数
  • 拷贝构造函数
  • 赋值构造函数
  • 访问控制和封装:
  • this:调用对象成员时,其本质是通过this访问该对象,当我们调用一个成员函数时,用请求该函数的对象地址初始化this。
代码语言:javascript
复制
std::string isbn() const { return this->bookNo; }

返回对象本身

代码语言:javascript
复制
Sale_data& Sale_data::combie(const Sale_data& rhs) {
    units_sold += rhs.units_sold;
    revenus += rhs.revenus;
    return *this;
}
  • 前向声明:对于一个类来说,在创建它的对象之前该类必须定义过,否则编译器不知道该分配多少内存给类的数据成员。但是针对某些情况,例如两个类互相依赖或者类的成员包含本身,这就需要使用类的指针或引用,对于未定义只声明的类在使用前需要向程序中引入前向声明。
代码语言:javascript
复制
lass B; //前向引用声明
class A//A类的定义
{
public://外部接口
    void f(B b);//以B类对象b为形参的成员函数
               
};

class B//B类的定义
{
public://外部接口
    void g(A a);//以A类对象a为形参的成员函数
};

Class与Stuct区别

Struct主要用于数据类型的定义,Class用于面向对象的实现,包含一组相关的数据和行为。

最主要的区别在于默认访问控制不一样:

  • 默认继承访问权限:struct 是 public 的,class 是 private 的
  • 默认成员访问权限:struct 是 public 的,class 是 private 的

顺序容器

vector

deque

list

forward_list

array

string

容器的拷贝——对象间数据分离

代码语言:javascript
复制
vector<string> v1;
{
    vector<string> v2 = {"a", "b", "an"};
    v1 = v2;
} // v2被释放,但是v1的数据还是存在,是之前v2元素的拷贝

内存分区

  • 堆(heap):程序通过(malloc/free, new delete)手动分配和释放;
  • 栈(stack):编译器自动分配释放;
  • 全局区/静态区:全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域,未初始化的全局变量和未初始化的静态变量在相邻的另一块区域,程序结束释放。
  • 常量区: 专门放常量的地方,程序结束释放。

动态内存

  1. 直接管理方式:new/delete,new在自由存储空间上分配对象内存,对象使用完毕需使用delete释放new分配的对象指针指定的内存空间;delete p释放对象,delete []释放p指向的数组。
  2. 智能管理方式:智能指针实现了动态内存的自动释放,通过make_shared或者new在内存中分配一个对象并初始化,在引用计数位0时释放对象存储空间。智能指针分为:shared_ptr/unique_ptr/weak_ptr三种;
  3. shared_ptr:多个智能指针共享一个对象,支持拷贝和赋值,每次拷贝或赋值后对象的引用计数加1,智能指针销毁一次,引用计数减1。不支持动态管理数组,需要提供删除器。
  4. unique_ptr:不支持拷贝和赋值,任何时刻只能有一个unique_ptr指向特定的对象;
  5. weak_ptr:为解决shared_ptr对象相互引用导致对象无法释放,衍生出weak_ptr,只使用内置指针,不参与shared_ptr整个引用计数计算过程,不控制所指对象生存期。
  6. 智能指针使用陷进
  • 同一内置指针值不能初始化多个智能指针,避免资源被多次释放
  • 智能指针get()返回的内置指针不能初始化或者reset另一个智能指针,也不允许手动delete get()返回的内置指针
  • 使用get()返回的内置指针,当智能指针引用计数为0时,该内置指针也将失效。

类设计的工具

拷贝、赋值、销毁

  • 拷贝构造函数:将一个对象作为非引用实参、将一个非引用对象直接作为函数返回值、用花括号列表初始化一个数组或者一个类成员时均使用了拷贝构造函数。
代码语言:javascript
复制
class Foo {
    public:
        Foo();
        Foo(Foo& other);    // 拷贝构造函数必须使用引用,避免循环拷贝
};

  • 赋值构造函数:重载运算符=,一般运算符=号返回左侧运算对象的引用。
代码语言:javascript
复制
class Foo {
    public:
        Foo();
        Foo& operator=(const Foo& other); // 重载赋值运算符
};

  • 析构函数:与构造函数做相反的工作,构造函数初始化类的非static数据成员,析构函数销毁非static数据成员

,而且数据成员的销毁顺序和在构造函数中初始化的顺序相反。

代码语言:javascript
复制
class Foo {
    public:
        Foo();
        ~Foo();    // 析构函数
};

  • 阻止拷贝/赋值:某些情况下需要禁止类对象的拷贝或者赋值,例如单例,可以在函数的参数列表后面加=delete指出我们希望它定义为删除的。
代码语言:javascript
复制
class Foo {
    public:
        Foo();
        Foo(Foo& other) = delete;    // 阻止拷贝
        Foo& operator=(const Foo& other) = delete; // 阻止赋值
        
         ~Foo() = defalut;    // 使用合成的析构函数
};

除了上述方式,类通过将其拷贝构造函数或者赋值构造函数声明为private成员可以阻止拷贝。

oop

封装

C++中封装通过对类的访问权限实现,类将客观事物抽象成数据成员和方法,并通过public,protected,private三种访问权限控制其他对象对类的访问和继承。

  • public 成员:可以被任意实体访问
  • protected 成员:只允许被子类及本类的成员函数访问
  • private 成员:只允许被本类的成员函数、友元类或友元函数访问

继承

  • 子类(派生类)可通过public、protected、private三种继承方式继承父类
  • public继承方式
    • 基类中所有 public 成员在派生类中为 public 属性;
    • 基类中所有 protected 成员在派生类中为 protected 属性;
    • 基类中所有 private 成员在派生类中不能使用。
  • protected继承方式
    • 基类中的所有 public 成员在派生类中为 protected 属性;
    • 基类中的所有 protected 成员在派生类中为 protected 属性;
    • 基类中的所有 private 成员在派生类中不能使用。
  • private继承方式
    • 基类中的所有 public 成员在派生类中均为 private 属性;
    • 基类中的所有 protected 成员在派生类中均为 private 属性;
    • 基类中的所有 private 成员在派生类中不能使用。

上述继承方式,默认使用private继承方式。

多态

  • 虚基类:让基类对象在内存中只存在一份
代码语言:javascript
复制
#include <iostream>

using namespace std;

class Base {
public:
    Base(){
        cout<< "Base" << endl;
    }
};

class DerivedA:  public Base{
public:
    DerivedA(){
        cout<< "Derived A" << endl;
    }
};

class DerivedB:  public Base{
public:
    DerivedB(){
        cout<< "Derived B"<<endl;
    }
};

class DerivedAll: public DerivedA,public DerivedB{
public:
    DerivedAll(){
        cout<< "Derived All"<<endl;
    }
};
int main()
{
   DerivedAll a;

    return 0;
}

结果输出:

代码语言:javascript
复制
Base
Derived A
Base
Derived B
Derived All

子类构造函数调用时调用了两次父类的构造函数,即存在两份父类对象在内存中,为了避免上述情况,引入虚基类,即继承前加上virtual。

代码语言:javascript
复制
class DerivedA:  virtual public Base{
public:
    DerivedA(){
        cout<< "Derived A" << endl;
    }
};

class DerivedB: virtual public Base{
public:
    DerivedB(){
        cout<< "Derived B"<<endl;
    }
};

现在运行结果如下:

代码语言:javascript
复制
Base
Derived A
Derived B
Derived All
  • 虚函数:借助虚函数可实现基类对象指针调用派生类对象对应的虚函数。
代码语言:javascript
复制
#include <iostream>

using namespace std;

class Base {
public:
    void virtual func(){
        cout<< "Base"<<endl;
    }
};

class DerivedA: public Base{
public:
    void func(){
        cout<< "Derived A"<<endl;
    }
};

int main()
{
   Base * pb = new DerivedA();
   pb->func();
    return 0;
}

上述代码打印结果是:

代码语言:javascript
复制
Derived A

如果 func不是虚函数,则pb指针对象使用的仍是基类指针对象,故调用的是基类的func函数。

  • 虚函数表:存储虚函数地址,由所有虚函数对象共用。

每个虚继承的子类都有一个虚基类指针(占用一个指针的存储空间,4字节)和虚基类表(不占用类对象的存储空间)

当派生类重新定义虚函数时,则将派生类的虚函数的地址添加到虚函数表中。当一个基类指针指向一个派生类对象时,虚函数表指针指向派生类对象的虚函数表。当调用虚函数时,由于派生类对象重写了派生类对应的虚函数表项,基类在调用时会调用派生类的虚函数,从而产生多态。

  • 虚析构函数:为了防止delete指向派生类对象的基类指针时只调用基类的析构函数引起内存泄漏
代码语言:javascript
复制
using namespace std;

class Base {
public:
    virtual ~ Base(){
        cout<< "delete Base" << endl;
    }
    virtual void func(){
        cout<< "Base func"<<endl;
    }
};

class Derived: public Base{
public:
    ~ Derived(){
        cout << "delete Derived"<< endl;
    }
    void func(){
        cout<< "Derived func2"<<endl;
    }
};

int main()
{
   Base * pb = new Derived();
   pb->func();
   delete pb;
    return 0;
}

运行结果如下:

代码语言:javascript
复制
Derived func2
delete Derived
delete Base

若Base的析构函数是普通函数,则delete pb时只会调用Base的析构函数

  • 纯虚函数:虚函数声明时候加上=0,包含纯虚函数的类是抽象类,不可实例化,纯虚函数必须被派生类实现。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。

如有侵权,请联系 cloudcommunity@tencent.com 删除。

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 基本类型
  • 变量
  • 复合类型
  • 左值引用&右值引用
  • const
  • explicit
  • 初始化列表
  • 代码结构
  • Class与Stuct区别
  • 顺序容器
  • 内存分区
  • 动态内存
  • 类设计的工具
  • 拷贝、赋值、销毁
  • oop
    • 封装
      • 继承
        • 多态
        相关产品与服务
        容器服务
        腾讯云容器服务(Tencent Kubernetes Engine, TKE)基于原生 kubernetes 提供以容器为核心的、高度可扩展的高性能容器管理服务,覆盖 Serverless、边缘计算、分布式云等多种业务部署场景,业内首创单个集群兼容多种计算节点的容器资源管理模式。同时产品作为云原生 Finops 领先布道者,主导开源项目Crane,全面助力客户实现资源优化、成本控制。
        领券
        问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档