欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

c++那些事 笔记

程序员文章站 2022-05-05 17:39:20
...

C++那些事

const 类型作用

  1. 修饰变量:常量(相比#define,可以节省空间,避免#define定义的常量在内存中有若干个拷贝;防止被修改;类型检查)
  2. 修饰指针:不同位置作用不同,在变量前代表指针不可改变,其他位置代表指针指向的内容不可变
  3. 修饰参数:不可修改参数
  4. 修饰函数:函数体不可修改类对象
  5. 修饰函数返回值:返回值不可变
  6. const对象默认为文件局部变量

类成员const变量只能在初始化列表进行初始化
this 指针默认是 T * const,对const函数会变成 const T const*
引用在设置后就不能修改了

内联函数

内联能提高函数效率,但并不是所有的函数都定义成内联函数!内联是以代码膨胀(复制)为代价,仅仅省去了函数调用的开销,从而提高函数的执行效率。

  1. 如果执行函数体内代码的时间相比于函数调用的开销较大,那么效率的收货会更少!
  2. 另一方面,每一处内联函数的调用都要复制代码,将使程序的总代码量增大,消耗更多的内存空间。

虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。

虚函数

构造函数可以为虚函数吗?
构造函数不可以声明为虚函数。同时除了inline之外,构造函数不允许使用其它任何关键字。
为什么构造函数不可以为虚函数?
尽管虚函数表vtable是在编译阶段就已经建立的,但指向虚函数表的指针vptr是在运行阶段实例化对象时才产生的。 如果类含有虚函数,编译器会在构造函数中添加代码来创建vptr。 问题来了,如果构造函数是虚的,那么它需要vptr来访问vtable,可这个时候vptr还没产生。 因此,构造函数不可以为虚函数。我们之所以使用虚函数,是因为需要在信息不全的情况下进行多态运行。而构造函数是用来初始化实例的,实例的类型必须是明确的。 因此,构造函数没有必要被声明为虚函数。

volatile

  • volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。
  • volatile 关键字声明的变量,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)
  • const 可以是 volatile (如只读的状态寄存器)
  • 指针可以是 volatile

位域

http://www.yuan-ji.me/C-C-%E4%BD%8D%E5%9F%9F-Bit-fields-%E5%AD%A6%E4%B9%A0%E5%BF%83%E5%BE%97/

explicit

  1. explicit 修饰构造函数时,可以防止隐式转换和复制初始化
  2. explicit 修饰转换函数时,可以防止隐式转换,但按语境转换除外

不允许类似B b2 = 1;的隐式转换。

using

  • 在继承过程中,派生类可以覆盖重载函数的0个或多个实例,一旦定义了一个重载版本,那么其他的重载版本都会变为不可见。
    如果对于基类的重载函数,我们需要在派生类中修改一个,又要让其他的保持可见,必须要重载所有版本,这样十分的繁琐。
#include <iostream>
using namespace std;

class Base{
    public:
        void f(){ cout<<"f()"<<endl;
        }
        void f(int n){
            cout<<"Base::f(int)"<<endl;
        }
};

class Derived : private Base {
    public:
        using Base::f;
        void f(int n){
            cout<<"Derived::f(int)"<<endl;
        }
};

int main()
{
    Base b;
    Derived d;
    d.f();
    d.f(1);
    return 0;
}
  • 取代typedef
    C中常用typedef A B这样的语法,将B定义为A类型,也就是给A类型一个别名B
    对应typedef A B,使用using B=A可以进行同样的操作。
typedef vector<int> V1; 
using V2 = vector<int>;

枚举类

/**
 * @brief C++11的枚举类
 * 下面等价于enum class Color2:int
 */
enum class Color2
{
    RED=2,
    YELLOW,
    BLUE
};
r2 c2 = Color2::RED;
cout << static_cast<int>(c2) << endl; //必须转!


可以指定用特定的类型来存储enum

enum class Color3:char;  // 前向声明

// 定义
enum class Color3:char 
{
    RED='r',
    BLUE
};
char c3 = static_cast<char>(Color3::RED);
  • 新的enum的作用域不在是全局的
  • 不能隐式转换成其他类型

引用与指针

引用 指针
必须初始化 可以不初始化
不能为空 可以为空
不能更换目标 可以更换目标

引用必须初始化,而指针可以不初始化。
我们在定义一个引用的时候必须为其指定一个初始值,但是指针却不需要。

int &r;    //不合法,没有初始化引用
int *p;    //合法,但p为野指针,使用需要小心

引用不能为空,而指针可以为空。
由于引用不能为空,所以我们在使用引用的时候不需要测试其合法性,而在使用指针的时候需要首先判断指针是否为空指针,否则可能会引起程序崩溃。

void test_p(int* p)
{
    if(p != null_ptr)    //对p所指对象赋值时需先判断p是否为空指针
        *p = 3;
    return;
}
void test_r(int& r)
{
    r = 3;    //由于引用不能为空,所以此处无需判断r的有效性就可以对r直接赋值
    return;
}

引用不能更换目标
指针可以随时改变指向,但是引用只能指向初始化时指向的对象,无法改变。

int a = 1;
int b = 2;

int &r = a;    //初始化引用r指向变量a
int *p = &a;   //初始化指针p指向变量a

p = &b;        //指针p指向了变量b
r = b;         //引用r依然指向a,但a的值变成了b

初始化列表与赋值

  • const成员的初始化只能在构造函数初始化列表中进行
  • 引用成员的初始化也只能在构造函数初始化列表中进行
  • 对象成员(对象成员所对应的类没有默认构造函数)的初始化,也只能在构造函数初始化列表中进行