C语言中文网-CPP教程

0.一些知识

  • const

    const int * const p;
    int cosnt * const p;
    
  • 内存对齐

  • 栈溢出

  • 内存池、池化技术(内存池、线程池)

  • 内存泄露

  • 静态链接库、动态链接库

  • C++内存分区:栈区、堆区、全局/静态区、常量区、代码区

  • 函数指针

1.从C到C++

1.1 学习C++之前要先学习C语言吗?

  • C++ 支持面向过程编程、面向对象编程和泛型编程,而C语言仅支持面向过程编程

1.4 C++命名空间

  • namespace
    using 
    using namespace
    

1.5 C++头文件和std命名空间

  • std:standard

1.9 C++中的const又玩出了新花样

  • C和C++中全局 const 变量的作用域相同,都是当前文件,不同的是它们的可见范围:C语言中 const 全局变量的可见范围是整个程序,在其他文件中使用 extern 声明后就可以使用;而C++中 const 全局变量的可见范围仅限于当前文件,在其他文件中不可见,这和添加了static关键字的效果类似,所以它可以定义在头文件中,多次引入后也不会出错。
  • extern:从别的文件引入到当前文件

1.10 C++ new和delete运算符简介

  • 和 malloc() 一样,new 也是在区分配内存,必须手动释放,否则只能等到程序运行结束由操作系统回收

1.11 C++内联函数

  • inline定义时写,声明时写没用

1.13 何规范地使用内联函数

  • inline函数最后直接将函数代码代替调用
  • 将内联函数作为带参宏的替代方案更为靠谱,而不是真的当做函数使用
  • 在多文件编程时,我建议将内联函数的定义直接放在头文件中,并且禁用内联函数的声明(声明是多此一举)

1.15 到底在什么地方指定默认参数

  • C++ 规定,在给定的作用域中只能指定一次默认参数

  • C语言有四种作用域,分别是函数原型作用域、局部作用域(函数作用域)、块作用域、文件作用域(全局作用域),C++ 也有这几种作用域

  • 多次声明同一函数:有一点需要注意,在给定的作用域中一个形参只能被赋予一次默认参数

    //多次声明同一个函数
    void func(int a, int b, int c = 36);
    void func(int a, int b = 5, int c);
    
    

1.17 函数重载过程中的二义性和类型转换

  • C++ 标准规定,如果有且只有一个函数满足下列条件,则匹配成功:
    1. 该函数对每个实参的匹配都不劣于其他函数;
    2. 至少有一个实参的匹配优于其他函数。

1.18 如何实现C++和C的混合编程?

  • extern "C":让编译器以处理C语言的方式来处理修饰的C++代码
    

2.类和对象

2.1 C++类的定义和对象的创建

  • Student stu在栈上分配内存
  • tudent *pStu 在堆上创建
  • 类只是一个模板(Template),编译后不占用内存空间,所以在定义类时不能对成员变量进行初始化,因为没有地方存储数据。只有在创建对象以后才会给成员变量分配内存,这个时候就可以赋值了

2.2 C++类的成员变量和成员函数

  • 类体中定义的成员函数会自动成为内联函数,在类体外定义的不会
  • 议在类体内部对成员函数作声明,而在类体外部进行定义

2.4 C++对象的内存模型

  • 对象的大小仅包含成员变量,成员函数是所有对象共用

2.6 C++构造函数

  • 一旦用户自己定义了构造函数,不管有几个,也不管形参如何,编译器都不再自动生成

  • 调用没有参数的构造函数也可以省略括号

    Student stu()或Student stu
    Student *pstu = new Student()或Student *pstu = new Student
    

2.7 C++构造函数初始化列表

  • 成员变量的初始化顺序与初始化列表中列出的变量的顺序无关,它只与成员变量在类中声明的顺序有关
  • 初始化 const 成员变量的唯一方法就是使用初始化列表

2.8 C++析构函数

  • 析构函数没有参数,不能被重载

  • 在所有函数之外创建的对象是全局对象,它和全局变量类似,位于内存分区中的全局数据区,程序在结束执行时会调用这些对象的析构函数;在函数内部创建的对象是局部对象,它和局部变量类似,位于栈区,函数执行结束时会调用这些对象的析构函数

  • new 创建的对象位于堆区,通过 delete 删除时才会调用析构函数;如果没有 delete,析构函数就不会被执行。

2.9 C++对象数组(数组的每个元素都是对象)

  • 在构造函数有多个参数时,数组的初始化列表中要显式地包含对构造函数的调用

    CTest arrayl [3] = { 1, CTest(1,2) };
    

2.10 C++成员对象和封闭类

  • 封闭类:有成员对象是其他类
  • 封闭类对象生成时,先执行所有成员对象的构造函数,然后才执行封闭类自己的构造函数

2.11 C++ this指针

  • this是一个 const 指针

2.12 C++静态成员变量

  • 静态成员变量必须初始化,而且只能在类体外进行

2.14 C++ const成员变量和成员函数

  • const 成员变量只有一种方法,就是通过构造函数的初始化列表

  • 常成员函数:

    void fun() const;
    
  • 需要强调的是,必须在成员函数的声明和定义处同时加上 const 关键字

2.15 C++ const对象

  • 定义常对象的语法和定义常量的语法类似

    const class object(params);
    class const object(params);
    
  • const对象只能访问const成员

2.16 C++友元函数和友元类

  • 友元函数不同于类的成员函数,在友元函数中不能直接访问类的成员,必须要借助对象

    class Student{
    public:
        Student(char *name, int age, float score);
    public:
        friend void show(Student *pstu);  //将show()声明为友元函数
    private:
        char *m_name;
        int m_age;
        float m_score;
    };
    void show(Student *pstu){
        cout<<pstu->m_name<<"的年龄是 "<<pstu->m_age<<",成绩是 "<<pstu->m_score<<endl;
    }
    void show(){
        cout<<m_name<<"的年龄是 "<<m_age<<",成绩是 "<<m_score<<endl;
    }
    
  • A里写的friend,friend的东西可以访问A的内容

2.17 类其实也是一种作用域

  • 静态成员既可以通过对象访问,又可以通过类访问,而 **typedef **定义的类型只能通过类来访问
  • 定义在类外的函数,参数不用A::,返回值要A::,因为返回值在函数名前

2.18 C++ class和struct到底有什么区别

  • 使用 class 时,类中的成员默认都是 private 属性的
  • 而使用 struct 时,结构体中的成员默认都是 public 属性的

2.19 C++ string(C++字符串)

  • string没有\0

  • 转换为C风格的字符串

    s.c_str()
    
  • string读取时遇到空格结束

2.20 C++ string的内部究竟是什么样的

  • char str[10] = “abc”,这样的字符串是可读写的;
  • char *str = “abc”,这样的字符串只能读,不能写。
  • string写时复制

3.C++引用

3.1 C++引用10分钟入门教程

  • 引用必须在定义的同时初始化,并且以后也要从一而终,不能再引用其它数据,类似于常量(const 变量)

  • 不希望通过引用来修改原始的数据

    const type &
    type const &
    
  • 不能返回局部变量的&

3.2 C++引用在本质上是什么,它和指针到底有什么区别?

  • 其实引用只是对指针进行了简单的封装,它的底层依然是通过指针实现的,引用占用的内存和指针占用的内存长度一样,在 32 位环境下是 4 个字节,在 64 位环境下是 8 个字节,之所以不能获取引用的地址,是因为编译器进行了内部转换

  • 有const指针,没有const引用

    int a = 20;
    int & const r = a;
    

3.3 C++引用不能绑定到临时数据

  • 寄存器:只能将较小的临时数据放在寄存器中
  • 内存:对象、结构体变量是自定义类型的数据,大小不可预测,通常在内存中
  • 常量表达式:不包含变量的表达式(34.5*23)
  • 形参为引用时,调用函数时不要传递表达式/临时变量

3.4 编译器会为const引用创建临时变量

  • 可以给const引用,传递表达式/临时变量(因为const引用只读)

3.5 C++ const引用与转换类型

  • 引用和被引用的变量类型必须严格一致
  • const引用可以类型转换
  • 引用形参尽量+const

4. 继承与派生

4.1 C++继承和派生简明教程

  • class默认private

4.2 C++三种继承方式

  • 实际上,基类的 private 成员是能够被继承的,并且(成员变量)会占用派生类对象的内存,它只是在派生类中不可见,导致无法使用罢了

  • using可以改变访问权限

    class Student : public People {
    public:
        void learning();
    public:
        using People::m_name;  //将protected改为public
        using People::m_age;  //将protected改为public
        float m_score;
    private:
        using People::show;  //将public改为private
    };
    

4.3 C++继承时的名字遮蔽问题

  • 遮蔽:成员直接遮蔽,函数只要重名直接遮蔽,不管参数问题(因为找的时候是按照作用域去找)

4.5 C++继承时的对象内存模型

  • 变量/函数被遮蔽后仍在派生类的内存模型中

4.6 C++基类和派生类的构造函数

  • 基类构造函数只能放在初始化成员列表中,不能放在函数体内
  • 不能调用间接基类的构造函数(虚继承存中必须显式调用基类的构造函数)
  • 构造函数不被继承,因为构造函数只能有一个

4.7 C++基类和派生类的析构函数

  • 每个类只有一个析构函数

4.8 C++多继承(多重继承)

  • 两个基类中有同名的成员,使用::,否则有二义性

4.10 借助指针突破访问权限的限制,访问private、protected属性的成员变量

  • 属性限制只是不能被访问,但是可以通过指针偏移访问

4.11 C++虚继承和虚基类详解

  • 虚继承的目的是让某个类做出声明,承诺愿意共享它的基类
  • 虚派生只影响从指定了虚基类的派生类中进一步派生出来的类,它不会影响派生类本身

4.12 C++虚继承时的构造函数

  • C++干脆规定必须由最终的派生类 D 来初始化虚基类 A,直接派生类 B 和 C 对 A 的构造函数的调用是无效的。

4.13 C++虚继承下的内存模型

  • 正常继承时,基类成员在最前;虚继承时,共享的部分(虚基类)追加在后面
  • cfront/VC解决方案

4.14 C++向上转型

  • 派生类可以赋给基类(向上转型),大材小用,不用的部分舍弃

5. C++多态与虚函数

  • 面向对象程序设计语言有封装、继承和多态三种机制,这三种机制能够有效提高程序的可读性、可扩充性和可重用性。

5.1 C++多态和虚函数快速入门教程

  • 有了虚函数,基类指针指向基类对象时就使用基类的成员(包括成员函数和成员变量),指向派生类对象时就使用派生类的成员。换句话说,基类指针可以按照基类的方式来做事,也可以按照派生类的方式来做事,它有多种形态,或者说有多种表现方式,我们将这种现象称为多态
  • 不过引用不像指针灵活,指针可以随时改变指向,而引用只能指代固定的对象,在多态性方面缺乏表现力,所以以后我们再谈及多态时一般是说指针
  • 多态可以增加其灵活性

5.2 C++虚函数注意事项以及构成多态的条件

  • 只有函数原型相同(返回值、参数均相同)的函数才构成多态
  • 构造函数不能为虚函数
  • 析构函数可以为虚函数
  • 构成多态的条件:
    1. 必须存在继承关系
    2. 继承关系中必须有同名的虚函数,并且它们是覆盖关系(函数原型相同)
    3. 存在基类的指针,通过该指针调用虚函数
  • 通过基类的指针只能访问从基类继承过去的成员,不能访问派生类新增的成员

5.3 C++虚析构函数的必要性

  • 构造函数不能是虚函数:
    1. 因为派生类不能继承基类的构造函数,将构造函数声明为虚函数没有意义
    2. C++ 中的构造函数用于在创建对象时进行初始化工作,在执行构造函数之前对象尚未创建完成,虚函数表尚不存在,也没有指向虚函数表的指针,所以此时无法查询虚函数表,也就不知道要调用哪一个构造函数
  • 大部分情况下都应该将基类的析构函数声明为虚函数

5.4 C++纯虚函数和抽象类

  • 纯虚函数、抽象类
  • 只有类中的虚函数才能被声明为纯虚函数,普通成员函数和顶层函数均不能声明为纯虚函数

5.5 C++虚函数表精讲教程,直戳多态的实现机制

  • 虚函数表
  • 虚函数表中只有当前类的虚函数

5.6 C++ typeid运算符:获取类型信息

  • 两种使用方法:

    typeid(dataType)
    typeid(expression)
    
  • 必须有(),sizeof可有可无

  • typeid 会把获取到的类型信息保存到一个 type_info类型的对象里面,并返回该对象的常引用

5.7 C++ RTTI机制精讲(C++运行时类型识别机制)

  • RTTI(Run-Time Type Identification、运行时类型识别)

  • C++ 的对象内存模型:

    1. 如果没有虚函数也没有虚继承,那么对象内存模型中只有成员变量。
    2. 如果类包含了虚函数,那么会额外添加一个虚函数表,并在对象内存中插入一个指针,指向这个虚函数表。
    3. 如果类包含了虚继承,那么会额外添加一个虚基类表,并在对象内存中插入一个指针,指向这个虚基类表。
  • 只有类中包含了虚函数时才会启用 RTTI 机制,其他所有情况都可以在编译阶段确定类型信息

5.8 C++静态绑定和动态绑定,彻底理解多态

  • 函数绑定:找到函数名对应的地址,然后将函数调用处用该地址替换
  • 静态绑定:在编译期间(包括链接期间)就能找到函数名对应的地址,完成函数的绑定,程序运行后直接使用这个地址即可
  • 动态绑定:必须要等到程序运行后根据具体的环境或者用户操作才能决定

5.9 C++ RTTI机制下的对象内存模型(透彻)

6. 运算符重载

6.1 C++运算符重载基础教程

  • 运算符重载函数不仅可以作为类的成员函数,还可以作为全局函数

6.2 运算符重载时要遵循的规则

  • 运算符重载函数不能有默认的参数,否则就改变了运算符操作数的个数,这显然是错误的
  • 运算符重载函数作为类的成员函数时,二元运算符的参数只有一个,一元运算符不需要参数(另一个参数隐含)
  • 运算符重载函数作为全局函数时,二元操作符就需要两个参数,一元操作符需要一个参数,而且其中必须有一个参数是对象
  • 将运算符重载函数作为全局函数时,一般都需要在类中将该函数声明为友元函数
  • 箭头运算符->、下标运算符[ ]、函数调用运算符( )、赋值运算符=只能以成员函数的形式重载

6.3 C++重载数学运算符

6.4 到底以成员函数还是全局函数(友元函数)的形式重载运算符

  • 转换构造函数

    img

  • 为什么以全局函数形式重载:为了保证运算符的操作数能够被对称的处理(C++ 只会对成员函数的参数进行类型转换,而不会对调用成员函数的对象进行类型转换)

  • 为什么以成员函数形式重载:暂无必须原因

6.5 C++重载>>和<<

  • 返回 istream 类对象的引用,是为了能够连续读取复数

6.6 C++重载[]

  • 应该同时提供以上两种形式

    返回值类型 & operator[ ] (参数);
    
    const 返回值类型 & operator[ ] (参数) const;
    //适应 const 对象,因为通过 const 对象只能调用 const 成员函数
    

6.7 C++重载++和–

  • operator++() //前置
    operator++ (int n) //后置
    

6.8 C++重载new和delete

  • 返回值都是void*类型
  • 第一个参数必须是 size_t(size_t 表示的是要分配空间的大小)

6.9 C++重载()

  • ():类型强制转换运算符

  • operator double() { return real; }  //重载强制类型转换运算符 double
    

6.10 注意事项以及汇总

7. 模板

  • 模板类是类模板实例化后的一个产物

7.1 C++函数模板入门教程

  • 类型的参数化:当发生函数调用时,编译器可以根据传入的实参自动推断数据类型
  • typename == class

7.2 C++类模板入门教程

  • 类外定义成员函数时仍然需要带上模板头

  • 指针定义时两边都要指明具体的数据类型,且要保持一致

    //赋值号两边的数据类型不一致
    Point<float, float> *p = new Point<float, int>(10.6, 109);
    //赋值号右边没有指明数据类型
    Point<float, float> *p = new Point(10.6, 109);
    

7.5 C++函数模板的实参推断

  • 普通函数的类型转换:

    1. 算数转换:例如 int 转换为 float,char 转换为 int,double 转换为 int 等。
    2. 派生类向基类的转换:也就是向上转型,请猛击《C++向上转型(将派生类赋值给基类)》了解详情。
    3. const 转换:也即将非 const 类型转换为 const 类型,例如将 char * 转换为 const char *。
    4. 数组或函数指针转换:如果函数形参不是引用类型,那么数组名会转换为数组指针,函数名也会转换为函数指针。
    5. 用户自定的类型转换。
  • 函数模板仅能进行「const 转换」和「数组或函数指针转换」

  • 当类型参数的个数较多时,就会有个别的类型无法推断出来,这个时候就必须显式地指明实参

    template<typename T1, typename T2> void func(T1 a){
        T2 b;
    }
    func(10);  //函数调用 ×
    func<int, int>(10); √
    func<int>(10);  //省略 T2 的类型 √
    

7.6 C++模板的显式具体化

  • 模板的显示具体化:让模板能够针对某种具体的类型使用不同的算法(函数体或类体不同)

  • 在 C++ 中,对于给定的函数名,可以有非模板函数模板函数显示具体化模板函数以及它们的重载版本,在调用函数时,显示具体化优先于常规模板,而非模板函数优先于显示具体化和常规模板

  • 类模板时类外定义函数需要加上模板头类模板的显示具体化时类外定义函数不需要加上模板头

  • 部分显示具体化(只具体化一部分参数)只能用于类模板,不能用于函数模板

  • template<class T1, class T2> class Point{
    
    };
    //T2必须写,要让编译器知道char*代表的是第一个还是第二个参数
    template<typename T2> class Point<char*, T2>{
    
    };
    

7.7 C++模板中的非类型参数

  • template<class T, int N> void func(T (&arr)[N]);
    //这样可以传数组了
    
  • 类外定义成员函数需要加上类模板的模板头

  • 非类型参数的限制:

    1. 当非类型参数是一个整数时,传递给它的实参,或者由编译器推导出的实参必须是一个常量表达式,例如10、2 * 30、18 + 23 - 4等,但不能是n、n + 10、n + m等
    2. 当非类型参数是一个指针(引用)时,绑定到该指针的实参必须具有静态的生存期;换句话说,实参必须存储在虚拟地址空间中的静态数据区。局部变量位于栈区,动态创建的对象位于堆区,它们都不能用作实参

7.8 C++模板的实例化

  • 模板实例化是按需进行的
  • 通过类模板创建对象时,一般只需要实例化成员变量和构造函数
  • 如果一个成员函数永远不会被调用,那它就永远不会被实例化

7.9 将C++模板应用于多文件编程

  • 不能将模板的声明和定义分散到多个文件中的根本原因是:模板的实例化是由编译器完成的,而不是由链接器完成的,这可能会导致在链接期间找不到对应的实例。

7.10 C++模板的显式实例化

  • 显式实例化的一个好处是,可以将模板的声明和定义(实现)分散到不同的文件中

  • extern template declaration;  //实例化声明
    template declaration;  //实例化定义
    extern template class Point<char*, char*>;
    template class Point<char*, char*>;
    

7.11 C++类模板与继承

  • 四种情况

7.13 C++类模板中的静态成员

  • 静态成员不共享:A<int> 和 A<double> 是两个不同的类。虽然它们都有静态成员变量 count,但是显然,A<int> 的对象 ia 和 A<double> 的对象 da 不会共享一份 count。

8. C++异常

8.1 C++异常处理入门

  • 程序的错误大致可以分为三种,语法错误、逻辑错误、运行时错误

  • try{
        // 可能抛出异常的语句
    }catch(exceptionType variable){
        // 处理异常的语句
    }
    
  • 如果不希望 catch 处理异常数据,也可以将 variable 省略

  • 发生异常时必须将异常明确地抛出,try 才能检测到;如果不抛出来,即使有异常 try 也检测不到

  • 从异常点跳转到 catch 所在的位置,位于异常点之后的、并且在当前 try 块内的语句就都不会再执行了;即使 catch 语句成功地处理了错误,程序的执行流也不会再回退到异常点,所以这些语句永远都没有执行的机会了

  • 发生异常后,程序的执行流会沿着函数的调用链往前回退,直到遇见 try 才停止

    #include <iostream>
    #include <string>
    #include <exception>
    using namespace std;
    void func_inner(){
        throw "Unknown Exception";  //抛出异常
        cout<<"[1]This statement will not be executed."<<endl;
    }
    void func_outer(){
        func_inner();
        cout<<"[2]This statement will not be executed."<<endl;
    }
    int main(){
        try{
            func_outer();
            cout<<"[3]This statement will not be executed."<<endl;
        }catch(const char* &e){
            cout<<e<<endl;
        }
        return 0;
    }
    
    输出:
    Unknown Exception
    

8.2 C++异常类型以及多级catch匹配

  • 我们可以将 catch 看做一个没有返回值的函数,当异常发生后 catch 会被调用,并且会接收实参(异常数据)。
  • catch在匹配过程中的类型转换
    1. 算数转换:例如 int 转换为 float,char 转换为 int,double 转换为 int 等。
    2. 向上转型:也就是派生类向基类的转换,请猛击《C++向上转型(将派生类赋值给基类)》了解详情。
    3. const 转换:也即将非 const 类型转换为 const 类型,例如将 char * 转换为 const char *。
    4. 数组或函数指针转换:如果函数形参不是引用类型,那么数组名会转换为数组指针,函数名也会转换为函数指针。
    5. 用户自定的类型转换。

8.3 C++ throw(抛出异常)

  • 异常规范(C++11已抛弃):在函数头和函数体之间,指明当前函数能够抛出的异常类型

  • //派生类虚函数的异常规范必须与基类虚函数的异常规范一样严格
    class Base{
    public:
        virtual int fun1(int) throw();
        virtual int fun2(int) throw(int);
        virtual string fun3() throw(int, string);
    };
    class Derived:public Base{
    public:
        int fun1(int) throw(int);   //错!异常规范不如 throw() 严格
        int fun2(int) throw(int);   //对!有相同的异常规范
        string fun3() throw(string);  //对!异常规范比 throw(int,string) 更严格
    }
    //声明与定义必须一样
    void func3() throw(float, char*);
    void func3() throw(float, char*) { }
    

8.4 C++ exception类

  • C++ exception类层次图

9. 面向对象进阶

9.1 C++拷贝构造函数(复制构造函数)

  • 对象创建的两个状态:分配内存空间,然后再进行初始化
  • 拷贝构造函数参数用const&(不改变对象,并且const 对象和非 const 对象都可以传递给形参,如果不是引用是当前类的对象,则一直递归调用拷贝构造函数)

9.2 到底什么时候会调用拷贝构造函数?

  • 初始化和赋值

    int a = 100;  //以赋值的方式初始化
    a = 200;  //赋值
    a = 300;  //赋值
    int b;  //默认初始化
    b = 29;  //赋值
    b = 39;  //赋值
    
  • 拷贝构造和=运算符

    Stu s2 = s1;
    s2 = s1;
    

9.3 C++深拷贝和浅拷贝

  • 浅拷贝:增加一个指针指向原来的
  • 深拷贝:增加一个指针并且申请新的内存,使这个指针指向新的内存
  • 如果一个类拥有指针类型的成员变量,那么绝大部分情况下就需要深拷贝,因为只有这样,才能将指针指向的内容再复制出一份来,让原有对象和新生对象相互独立,彼此之间不受影响。如果类的成员变量没有指针,一般浅拷贝足以
  • 另外一种需要深拷贝的情况就是在创建对象时进行一些预处理工作,比如统计创建过的对象的数目、记录对象创建的时间等,请看下面的例子:

9.4 C++重载=(赋值运算符)

  • 初始化:在定义的同时进行赋值
  • 赋值:定义完成以后再赋值
  • 当类持有其它资源时,例如动态分配的内存、打开的文件、指向其他数据的指针、网络连接等,默认的赋值运算符就不能处理了,我们必须显式地重载它,这样才能将原有对象的所有数据都赋值给新对象
  • operator=() 的返回值类型为Array &:返回数据时避免拷贝还能达到连续赋值的目的
  • perator=() 的形参类型为const Array &:传参时避免调用拷贝构造还能接受const非const实参
  • 赋值运算符也能有其他参数,但必须有默认值

9.5 C++拷贝控制操作(三/五法则)

  • 拷贝构造函数、赋值运算符、析构函数、移动构造函数、移动赋值运算符
  • 需要析构函数的类也需要拷贝和赋值操作
  • 需要拷贝操作的类也需要赋值操作,反之亦然

9.6 C++转换构造函数

  • 其它类型转换为当前类类型
  • 需要注意的是,为了获得目标类型,编译器会“不择手段”,会综合使用内置的转换规则和用户自定义的转换规则,并且会进行多级类型转换(eg:char -> int -> double -> Complex)
  • 四种构造函数:默认构造函数、普通构造函数、拷贝构造函数、转换构造函数(可以融合,通过给参数默认值)

9.7 C++类型转换函数

  • 当前类类型转换为其它类型
  • 类型转换函数和运算符的重载非常相似,都使用 operator 关键字,因此也把类型转换函数称为类型转换运算符
  • 说明:
    1. type 可以是内置类型、类类型以及由 typedef 定义的类型别名,任何可作为函数返回类型的类型(void 除外)都能够被支持。一般而言,不允许转换为数组或函数类型,转换为指针类型或引用类型是可以的。
    2. 类型转换函数一般不会更改被转换的对象,所以通常被定义为 const 成员函数
    3. 类型转换函数可以被继承,可以是虚函数
    4. 一个类虽然可以有多个类型转换函数(类似于函数重载),但是如果多个类型转换函数要转换的目标类型本身又可以相互转换(类型相近),那么有时候就会产生二义性。

9.8 再谈C++转换构造函数和类型转换函数(进阶)

  • 一个类同时存在转换构造函数和类型转换函数的,就有可能产生二义性
  • 解决二义性问题的办法也很简单粗暴,要么只使用转换构造函数,要么只使用类型转换函数
  • 实践证明,用户对转换构造函数的需求往往更加强烈,把当前类型转换成其他类型用getxxx()

9.9 C/C++类型转换的本质(经典之作)

  • 隐式类型转换(更加安全)、显示类型转换(灵活性更强)
  • 隐式类型转换除了会重新解释数据的二进制位,还会利用已知的转换规则对数据进行恰当地调整;而显式类型转换只能简单粗暴地重新解释二进制位,不能对数据进行任何调整

9.10 C++四种类型转换运算符

  • xxx_cast<newType>(data)
    
  • static_cast、dynamic_cast、const_cast、reinterpret_cast

10. 输入输出流

11. 文件操作

12. C++多文件编程

  • .h .cpp

  • 防止头文件被重复引入的3种方法:

    #ifndef _NAME_H
    #define _NAME_H
    //头文件内容
    #endif
    
    #pragma once
    
    _Pragma("once")
    
  • 命名空间(namespace)常位于 .h 头文件中

  • extern告诉编译器存在着一个变量或者一个函数

  • const常量如何在多文件编程中使用

    const常量定义在.h头文件中
    
    借助extern先声明再定义const常量(.h里声明,.cpp里定义)
    
    借助extern直接定义const常量(.cpp里直接定义)
    
  • 预处理、编译、汇编和链接(.cpp需要这四个步骤,.h什么都不用,因为预处理生成.i已经把.h加到了.cpp前面)(.i、.s、.o、.exe)

  • 文件中应该只放变量和函数的声明,而不能放它们的定义。因为一个头文件的内容实际上是会被引入到多个不同的 .cpp 文件中的,并且它们都会被编译。如果在头文件中放了定义,就等同于在多个 .cpp 文件中出现对同一个符号(变量或函数)的定义,纵然这些定义的内容相同,编译器也不认可这种做法(报“重定义”错误)。

  • 三种例外可以在.h里放定义:const对象、内联函数、定义类

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Prince_H_23

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值