C++学习笔记

入门

hello world

#include <iostream> //标准输入输出流,等价于c语言的stdio.h

using namespace std; //使用标准命名空间,否则需要使用std::cout

int main() {
    /**
     * cout: 标准输出流对象
     * <<: 在C++中,用于在cout后拼接输出的内容(字符串拼接)
     * endl: end line,表示刷新缓冲区并换行
     */
    cout << "hello world\n"; //默认不带换行符
    cout << "hello world" << endl; //换行
    cout << "hello " << "world " << 123; //字符串拼接
    return EXIT_SUCCESS; //0,正常退出
}

::

#include <iostream>

int a = 1000;

int main() {
    int a = 2000;
    std::cout << "a = " << a << std::endl; //2000
    std::cout << "a = " << ::a << std::endl; //1000,::表示作用域,如果前面什么都没有添加,则表示全局作用域
    return EXIT_SUCCESS;
}

namespace

  • 声明

    命名空间只能在全局作用域中声明

  • 同名函数
    • game1.h
      #ifndef COURSE_GAME1_H
      #define COURSE_GAME1_H
      #include <iostream>
      using namespace std;
      namespace GAME1 {
          void play() { cout << "play game1" << endl; };
      }
      
      #endif //COURSE_GAME1_H
      
    • game2.h
      #ifndef COURSE_GAME2_H
      #define COURSE_GAME2_H
      #include <iostream>
      using namespace std;
      namespace GAME2 {
          void play() { cout << "play game2" << endl; };
      }
      
      #endif //COURSE_GAME2_H
      
    • main.cpp
       #include "ns/game1.h"
       #include "ns/game2.h"
       int main() {
           GAME1::play();
           GAME2::play();
           return 0;
       }
      
  • 可存放内容
    namespace GAME1 {
        int a = 0; //变量
        void play(); //函数
        struct A {}; //结构体
        class B {}; //类
        namespace GAME1_1 { //嵌套命名空间
            int aa = 100;
        }
    };
    
  • 可随时添加新成员
    namespace A {
    	int a = 1;
    }
    namespace A {
    	//int a = 2; //报错
    	int b = 2; //命名空间是开放的,可随时添加新成员
    }
    
  • 匿名命名空间
    namespace {
    	int t = 10000; //相当于 static int t = 10000;
    }
    void test {
    	cout << t << ::t << endl; //由于是全局作用域,可以直接调用,或者使用双冒号调用
    }
    
  • 别名
    namespace aaaaaaaaa {
    	int t = 10000;
    }
    namespace a = aaaaaaaaa;
    void test {
    	cout << a::t << endl; 
    }
    

using

  • 声明
    namespace A {
        int a = 1;
    }
    
    //声明
    void declare1(){
        using A::a;
        std::cout << a << std::endl;
    }
    void declare2(){
        int a = 2;
        using A::a; //报错
        std::cout << a << std::endl;
    }
    
  • 编译指令
    namespace A {
        int a = 1;
    }
    
    //编译指令
    void instruct() {
        int a = 2;
        using namespace A; //不报错
        std::cout << a << std::endl; //2,优先就近原则的变量
    }
    

C的扩展与增强

一、全局变量检测

  • c语言
    int a;
    int a = 1;
    //可正常运行
    
  • c++

    对这种重复定义的全局变量,会在编译时检测到并报错

二、函数检测

包括:形参类型检测、实参个数检测、返回值检测

  • c语言
    getResult(w, h) { //未定义类型与返回值(甚至定义了返回值,但是没有return代码也不会报错)
    	return w * h;
    }
    void test() {
    	printf("%d\n", getResult(10, 10, 10)); //100,参数个数不一致也可以正常执行并返回
    }
    
  • c++
    int getResult(int w, int h) {
    	return w * h;
    }
    void test() {
    	int result = getResult(10, 10);
    }
    

三、类型转换检测

  • c语言
    char * p = malloc(64); //malloc返回的是void*,但是可以用其他,如char*去接收
    
  • c++
    char * P = (char *)malloc(64); //必须进行强转
    

四、结构体

  • c语言
    //1. 结构体中不允许有函数
    //2. 创建结构体变量必须要有struct关键字
    struct Person {
    	int age;
    	//void func(); //报错,不允许有函数
    }
    void test(){
    	struct Person P; //必须有struct关键字
    	p.age = 100;
    }
    
  • c++
    struct Person {
    	int age;
    	void func(); //可以有函数声明
    	void func2() { //也可以有函数实现
    		age++;
    	};
    }
    void test(){
    	Person P; //可以省略掉struct关键字
    	p.age = 100;
    	p.func2();
    	cout << p.age << endl; //101
    }
    

五、布尔类型

c++新增bool布尔类型

bool flag; //默认 false,false-0;true-1
flag = 100;
cout << flag; //1,将非0值都转为1
cout << sizeof(bool); //1,只有一个字节

六、三目运算

  • c语言
    void test(){
    	int a = 10;
    	int b = 20;
    	printf("ret = %d\n", a > b ? a : b); //ret = 20
    	//a > b ? a : b = 100; //报错,c语言中返回的是值,即 20 = 100 这种语法是错误的
    	*(a > b ? &a : &b) = 100
    	printf("%d\n", b); //100
    }
    
  • c++
    void test(){
    	int a = 10;
    	int b = 20;
    	printf("ret = %d\n", a > b ? a : b); //ret = 20
    	a > b ? a : b = 100; //通过,c++中返回的是变量,即 b = 100 这种语法是可以的
    	cout << b << endl; //100
    }
    

七、const

  • c语言
    //a.c
    const int ttt = 1000; //c语言中const修饰的全局变量默认是外部链接属性
    
    //b.c
    const int a = 100; //全局const
    void test1(){
    	//a = 200; //编译就报错,常量无法直接修改
    	int *p = &a;
    	*p = 200; //运行时报错
    }
    void test2(){
    	const int a = 100; //局部const
    	int *p = &a;
    	*p = 200; //修改成功,所以说const是个伪常量
    	printf("%d\n", a); //200
    	int arr[a]; //不允许,报错,因为a是个伪常量
    }
    void test3(){
    	extern const int ttt; //无需引入头文件之类的
    	printf("%d\n", ttt); //100,可以访问成功
    }
    
  • c++
    //a.cpp
    const int ttt = 1000; //c++中const修饰的全局变量默认是内部链接属性
    extern const int tt = 10000; //可以通过extern关键字提升为外部链接属性
    
    //b.cpp
    const int a = 100; //全局const,同c语言
    void test1(){
    	//a = 200; //编译就报错,常量无法直接修改
    	int *p = (int *)&a; //直接&a返回的是const int *
    	*p = 200; //运行时报错
    }
    void test2(){
    	const int a = 100; //局部const
    	int *p = (int *)&a; //存在隐式操作:int temp = a; int *p = (int *)&a;
    	*p = 200;
    	cout << a << endl; //100,运行不报错,但是修改的是隐式操作中的变量值
    	int arr[a]; //可以,c++下const是常量,允许初始化数组
    }
    void test3(){
    	extern const int ttt;
    	cout << tt <<< endl; // 10000
    	cout << ttt <<< endl; //运行时报错(链接阶段报错):1个无法解析的外部命令
    }
    
    • 内存分配
      • const变量取地址,会分配临时内存,不可以修改
        void test(){
        	const int a = 10;
        	int *p = (int *)&a; //int temp = a; int *p = (int*)&temp;
        	*p = 10000;
        	cout << a << endl; //10
        }
        
      • 通过普通变量来初始化const变量,会分配内存,可修改
        void test(){
        	int b = 10;
        	const int a = b;
        	int *p = (int *)&a;
        	*p = 10000;
        	cout << a << endl; //10000
        }
        
      • 对于自定义数据类型,会分配内存,可修改
        #include <string>
        struct Person {
        	string name;
        	int age;
        }
        void test(){
        	const Person p;
        	//p.age = 10; //不可以直接修改
        	Person * pp = (Person *)&p;
        	(*pp).name = "Tom";
        	pp -> age = 10;
        	cout << p.name << endl; //Tom
        	cout << p.age << endl; //10
        }
        
  • c++中尽量用const而非宏定义
  1. const有类型,但是#define没有
    #define MAX 1024;
    //一旦出现报错,报错中看到的是1024,不利于问题定位
    
  2. const有作用域,但是```#define``不重视作用域

    #define默认定义到文件结尾,如果定义在指定作用下有效的常量,那么#define就不能用了

引用

引用的本质

引用的本质是在c++内部实现的一个指针常量

Type &ref = val; // Type *const ref = &val;

变量的引用

void test(){
	int c = 10;
	int a = 10;
	int &b = a; //&符号:表示b是对a的一个引用(一个别名的意思)
	//int &c; //报错,引用必须申明时初始化
	// &b = c; //运行报错,引用一旦初始化后就不可以引用其他变量
	b = 100;
	cout << a << endl; //100
	cout << b << endl; //100
}
int main(){
	test();
	return 0;
}

数组的引用

  • 基本用法
  1. 直接建立引用
    void test(){
    	int arr[10];
    	int (&pArr)[10] = arr;
    	for(int i = 0; i < 10; i++) {
    		arr[i] = 100 + i;
    	}
    	for(int i = 0; i < 10; i++) {
    		cout << pArr[i] << endl;
    	}
    }
    
  2. 先定义数组类型,再通过类型定义引用
    void test(){
    	int arr[10];
    	for(int i = 0; i < 10; i++) {
    		arr[i] = 100 + i;
    	}
    	typedef int(ARRAY_TYPE)[10]; //定义好类型
    	ARRAY_TYPE &pArr = arr;
    	for(int i = 0; i < 10; i++) {
    		cout << pArr[i] << endl;
    	}
    }
    
  • 参数的传递方式:引用

    参数的传递方式有:值传递、地址传递、引用传递

    void test(int &a, &b){
    	int temp = a;
    	a = b;
    	b = temp;
    }
    int main(){
    	int a = 10;
    	int b = 20;
    	test(a, b);
    	cout << a << endl; //20
    	cout << b << endl; //10
    }
    

指针的引用

  • c语言
    /*
     * p: 二级指针,指向指针的指针
     * *p: 指针,指向Person
     * **p: Person
     */
    struct Person {
    	int age;
    }
    void allocateSpace(Person **p){
    	*p = (struct Person *)malloc(sizeof(struct Person));
    	(*p) -> age = 10;
    }
    void test(){
    	struct Person *p = Null;
    	allocateSpace(&p);
    	printf("%d\n", p -> age) //10
    }
    
  • c++
    //无需高级指针(一级用二级),用同级指针操作即可
    struct Person {
    	int age;
    }
    void allocateSpace(Person* &pp){ // Person * &pp = p;
    	pp = (Person *)malloc(sizeof(Person));
    	pp -> age = 20;
    }
    void test(){
    	struct Person *p = Null;
    	allocateSpace(&p);
    	cout << p->age << endl; //20
    }
    

常量的引用

void test(){
	const int &ref = 10; //加入const后,进行了一个隐式操作(分配到一个临时空间):int temp = 10; const int &ref = temp;
	int *p = (int *)&ref;
	*p = 10000;
	cout << ref << endl; //10000,这个10是temp临时空间的,即通过普通变量来初始化const变量,所以可以进行修改
}

//使用场景:修饰函数形参,防止误修改操作
void test2(const int &a){
	a = 10000; //报错,无法修改常量
}

引用的注意事项

  1. 引用的必须是一块合法的内存空间
    int &a = 10; //语法错误,10不是一个合法的内存空间
    const int &a = 10; //可以,详见常量的引用
    
  2. 不要返回局部变量的引用
    int& func(){
    	int a = 10;
    	return a;
    }
    int& func2(){
    	static int a = 10;
    	return a;
    }
    int main(){
    	int &ref = func();
    	cout << ref << endl; //func结束局部变量释放,可能是随机的一个乱码值
    	
    	int &ref2 = func2();
    	cout << ref2 << endl; //10,通过static修饰符,变量不会被函数释放
    	func2() = 1000; //此时还可作为左值进行修改
    	cout << ref2 << endl; //1000
    	return 0;
    }
    

函数

内联函数

  • 宏函数的缺陷
  1. 必须通过加括号来确保运算的完整
    #include <iostream>
    #define MY_ADD(x, y) x + y
    #define MY_ADD2(x, y) ((x) + (y))
    
    void test_defect1(){
        int a = 10;
        int b = 20;
        int ret = MY_ADD(a, b) * 20;
        int ret2 = MY_ADD2(a, b) * 20;
        std::cout << ret << std::endl; //410: 10 + 20 * 20
        std::cout << ret2 << std::endl; //600: (10 + 20) * 20
    }
    
  2. 即使加了括号,某些运算依旧不符合预期
    #define MY_COMPARE(x, y) (((x) < (y)) ? (x) : (y))
    
    void test_defect2() {
        int a =10;
        int b = 20;
        int ret = MY_COMPARE(++a, b);
        std::cout << ret << std::endl; //预期11,实际12: (((++x) < (y)) ? (++x) : (y))
    }
    
  • 内联函数

    本身也是一个真正的(普通)函数,具有普通函数的所有行为,但是它又会在适当的地方像预定义宏一样展开,从而免去函数调用的开销

    /**
     * 1. 在普通函数前面加上 inline 关键字即可变为内联函数
     * 2. 内联函数的声明和定义必须在一起,否则编译器将视为普通函数存在(或者声明和实现同时加关键字)
     * 3. 内联函数的确会占用空间,但是省去了普通函数调用的压栈、跳转、返回等的开销,相当于是用空间换时间
     */
    inline void test_inline1();
    inline void test_inline1(){}
    inline int test_inline2(int a, int b) { return a + b; }
    
  • 类函数

    在类内部定义的函数(包括构造函数),都隐式加了inline关键字,即都会自动成为内联函数

  • 内联函数与编译器

    内联函数只是给编译器一个建议,编译器不一定会接受,而且即使没有将函数声明为内联函数,编译器也可能会将函数进行内联编译


    以下情况编译器可能考虑不进行内联编译:

    1. 存在任何形式的循环语句
    2. 存在过多的条件判断语句
    3. 函数体过于庞大
    4. 对函数进行了取地址操作(因为直接跑的源码,没有函数入口)

函数的参数默认值与占位参数

  • 参数默认值
    int func1(int a = 10) {}
    
    //以下会报错,声明和实现只能有一个提供参数默认值,不然会不知道应该用声明的参数a的值,还是定义中参数a的值
    int func2(int a = 10);
    int func2(int a = 10){}
    
  • 占位参数
void func1(int a, int) {}

void func2(int = 1) {} //占用参数也可以设置默认值

void test(){
	func1(10, 1); //占位参数必须传入
}

函数重载

指在同一个作用域下,允许参数(个数、类型、顺序)不同、函数名相同的多个函数存在

//int func(){} //类型不可以
void func(){}
void func(int a){}
void func(double a){}
void func(double a, int b){}
void func(int a, double b){}
注意点
void func(int &a) {
	cout << a << endl;
}

void func(const int &a) {
	cout << a << endl;
}

//注意一:避免二义性出现
//void func(int a){} //虽然可以和上面两个同时存在,但是调用时存在多个可调用,会报错,所以和第一个最好只有一个在

void test1(){
	int a = 10;
	func(a); //调用的是第一个
	func(10); //调用的是第二个
}

//注意二:默认参数也要避免二义性
void func1(int a, int b = 10){}
void func1(int a)

void test2(){
	func1(10); //报错,两个函数都可以调用,出现了二义性
}
实现原理

为了实现函数重载,编译器会做一些幕后工作,对于不同参数的同名函数,比如void func(),编译器可能会将其函数名修饰成_func,当遇到void func(int a),则将函数名修饰为_func_int,以此类推,当然这里只是说可能,因为不同的编译器可能有不同的修饰函数名的标准

extern "C"

由于c++中存在函数重载,编译器会将函数名进行修饰,而如果调用的函数来自c,将会出现找不到函数的错误,此时就需要使用externC来申明函数来自外部,需要从外部找,且链接方式为c语言的链接方式

  • test.h
    #include <stdio.h>
    void show();
    
  • test.c
    #include "test.h"
    void show(){
    	printf("hello c");
    }
    
  • main.cpp
    //#include "test.h" //使用extern C后会进行引入,不用再引入
    extern "C" void show(); //告诉编译器,show函数用C语言方式进行链接
    
    int main(){
    	show(); //如果不使用extern C,由于C++的函数重载,链接时查找的函数名可能就是_show_void,显然c文件中并不存在该函数,导致连接失败而报错
    	return 0;
    }
    
  • .h文件中进行extern C

    .cpp中进行extern C需要罗列所有所需的函数,十分的不便,因此,一般都是在.h中进行

    //test.h
    //表示如果是c++语言的话,就将代码放入 extern "C" { //... } 中
    #ifdef __cplusplus
    extern "C" {
    #endif
    
    # include <stdio.h>
    void show();
    
    #ifdef __cplusplus
    }
    #endif
    

类和对象

structclass的区别

class默认是private,而struct则是public

权限

  • public:类内类外皆可访问
  • protected:只有类内可以访问(子类可访问)
  • private:只有类内可以访问(子类不可以访问)
class Person {
public:
	string name; //public
	int age; //public
protected: 
	string car; //protected
private:
	int pwd; //private
public:
	void func() { //public
		name = "张三"
	}
}

构造函数

对象的初始化,同java

class Person {
public:
	Person() { //... } //无参构造
	Person(paramType param, ...) {} //有参构造
}
分类
  • 按照参数分类
  1. 无参构造(默认构造函数)
  2. 有参构造
  • 按照类型分类
  1. 普通构造
  2. 拷贝构造
    /*
     * const:防止拷贝的对象被修改
     * &:采用引用传递的方式而不是值传递的方式,防止无限套娃,因为值传递实质就是调用的拷贝构造函数
     */
    class Person(const Person &p) {
    	attr1 = p.attr1;
    	//...
    }
    
调用
class Person {
public:
	string name;
	int age;
	Person() {} //无参构造
	Person(string n, int a) { //有参构造
		name = n;
		age = a;
	}
	Person(const Person &p){ //拷贝构造
		name = p.name;
		age = p.age;
	}
	explicit Person(int a) { //使用explicit关键字,可以禁止隐式调用法
		age = a;
	}
}
括号法调用
void test(){
	Person p("张三", 10); //有参构造
	Person p2(p); //拷贝构造
}
显示法调用
void test(){
	Person p = Person("张三", 10); //有参构造
	Person p2 = Person(p); //拷贝构造
}
隐式类型转换法
void test(){
	//不建议使用
	Person p = ("张三", 10); //有参构造
	Person p2 = p; //拷贝构造 === 由此可见,值传递本质就是调用拷贝构造
}
无参构造的调用

不要使用括号法,会被当成函数声明

void test(){
	Person p;
	//Person p() //会被当做是test函数中进行的一个:返回值为Person类型,函数名为p 的函数声明
}
匿名对象

特点:当前行执行完后立即释放

注意:不要用拷贝构造来初始化匿名对象,否则会不知道

class Person{
public:
	int age;
	Person(int a){ age = a; }
    Person(const Person &p) { age = p.age }
}

void test(){
	Person(10); //匿名对象,当前行执行完后立即释放
	Person p = Person(10);
	Person(p); //会报错,会被编译器认为在创建一个p对象,但是p对象已经存在,所以抛出Person p重定义错误
}
拷贝构造的调用时机
  1. 用已创建好的对象来初始化新的对象
    void test(){
    	Person p(10);
    	Person p2 = Person(p); //会触发拷贝构造
    }
    
  2. 用值传递的方式进行参数传递
    void func(Person p) {} //相当于Person p = p1,所以这里会触发拷贝构造
    void test(){
    	Person p1(10);
    	func(p1);
    }
    
  3. 以值的方式返回局部对象
    Person func() {
    	Person p;
    	return p; //p属于栈中对象,方法结束随之销毁,这里会隐式地用p拷贝出一个匿名对象并返回,会触发拷贝构造
    }
    void test(){
    	Person p = func();
    }
    

    注意:编译器是可能会进行优化的,如将debug版本改为release版本,上面的代码可能就会被优化为

    void func(Person &p){}; //将函数func()优化成这样
    
    Person p;
    func(p); //调用时创建一个p对象,然后作为参数传入,这样一来就不会调用到拷贝构造函数了
    
初始化列表
class Family{
public:
	int count;
	Family(int c): count(c) {}
}

class Person{
public:
	string name;
	Family family;
	Person(): name("张三"), family(20) { //会调用Family的有参构造
	}
	Person(string n, int count): name(n), family(count) {
	}
}

void test(){
	Person p;
	cout << p.name << endl; //"张三"
}

析构函数

对象的清理

class Person {
	/*
	 * 1.无返回值
	 * 2.函数名同类名,加上前缀符号~
	 * 3.不可以有参数、不可以重载
	 * 4.对象释放时编译器自动调用一次,如将对象放在方法中,方法结束时就会调用
	 */
	 ~Person() {
		//...
	}
}

默认函数

默认情况下,编译器会自动添加这四个函数:默认构造函数(空实现)、拷贝构造函数(值拷贝:浅拷贝)、析构函数(空实现)、operator=

如果用户自行添加了(有参|拷贝)构造函数,编译器将不会自行添加默认构造函数

深浅拷贝

浅拷贝的问题与析构函数的用途

通常可以用析构函数来释放堆内存

以下代码会报错:通过有参构造,给pname存放在堆中,p2通过默认的拷贝构造函数浅拷贝了p,即二者共用着堆中的这个name,当函数test_copy调用结束,p2(栈先进后出,所以先p2)触发析构函数,将name释放,但是紧接着p又来释放一次,导致重复释放的错误

#include <iostream>
#include <string.h>
using namespace std;

class Person{
public:
    char * name;
    int age;
    Person(const char* n, int a) {
        name = (char *)malloc(strlen(n) + 1);
        strcpy(name, n);
        age = a;
    }
    ~Person(){
        if (name != nullptr) {
            free(name);
            name = nullptr;
        }
    }
};

void test_copy() {
    Person p("张三", 20);
    cout << p.name << endl;
    Person p2(p);
    cout << p2.name << endl;
}

int main(){
    test_copy();
    return EXIT_SUCCESS;
}
采用深拷贝的解决方案

通过自行定义拷贝构造,对对象进行深拷贝

#include <iostream>
#include <string.h>
using namespace std;

class Person{
public:
    char * name;
    int age;
    Person(const char* n, int a) {
        name = (char *)malloc(strlen(n) + 1);
        strcpy(name, n);
        age = a;
    }
    Person(const Person &p) {
        name = (char *)malloc(strlen(p.name) + 1);
        strcpy(name, p.name);
        age = p.age;
    }
    ~Person(){
        if (name != nullptr) {
            free(name);
            name = nullptr;
        }
    }
};

void test_copy() {
    Person p("张三", 20);
    cout << p.name << endl;
    Person p2(p);
    cout << p2.name << endl;
}

int main(){
    test_copy();
    return EXIT_SUCCESS;
}

动态创建对象

c语言的动态分配内存
//分配
char* name = (char*)malloc(strlen("张三") + 1);
if (NULL == name) return 0; //可能分配失败

//释放
if (NULL != name) {
	free(name);
	name = NULL;
}
  • 缺点
  1. 必须确定对象的长度
  2. 必须对返回的void *类型进行强转
  3. 可能申请内存失败,必须对分配成功与否进行判断
c++newdelete
  • 作用

    new:用于分配(堆中)内存

    delete:用于释放(堆中)内存

    class Person {
    public:
    	Person(){}
    	
    	~Person(){}
    }
    
    void test(){
    	Person * p = new Person(); //在堆中创建了一个Person对象p
    	delete p; //释放堆中的Person对象p
    }
    
  • mallocfree的区别

    1. mallocfree属于库函数,newdelete属于运算符
    2. malloc不会调用构造函数,new可以
    3. malloc返回的是void*new直接返回对应的对象类型
  • 注意事项

    不要用万能指针void*去接收new的对象,会导致delete不知道要释放多少内存而失效

  • 数组的创建与销毁

    //堆上:一般类型
    int* arr = new int[10];
    
    //堆上:复杂类型
    Person* p = new Person[10]; //会调用(10次)默认构造,所以最好给创建的class都手动添加上默认构造
    Person* p = new Person[10]{Person(10), Person(20)}; //不建议,不是所有编译器都支持这种语法
    delete [] p; //默认只会释放一个导致释放不干净而报错(有些编译器没有报错提示但实质还是有问题的),所以要加上[]
    
    //栈上开辟数组:此时可以没有默认构造(不会自动去调用默认构造并创建对象)
    Person[10] p2 = {Person(10), Person(20)};
    

static

在类定义中,它的成员(包括成员变量和成员函数)如果加上static关键字,表示声明为静态的,称为静态成员

无论这个类创建了多少对象,静态成员都只存在一个拷贝,且这个拷贝被所有属于这个类的对象共享

静态成员变量
#include<iostream>
using namespace std;

class Person {
public:
    //1.声明:编译阶段就分配了内存(输出在main函数之前)
    static int age;
};
int Person::age = 0; //2.初始化:类内声明、类外初始化

int main(){
    //3.访问:通过对象访问
    Person p1;
    cout << p1.age << endl;

    //3.访问:通过类名访问(推荐)
    cout << Person::age << endl;
    return EXIT_SUCCESS;
}
静态成员函数
#include<iostream>
using namespace std;

class Person {
public:
	int age;
    static void func(){ //所有对象共享此函数
    	//age = 10; //报错,静态函数中只能访问静态变量,无法直接访问普通变量
        cout << "static func" << endl;
    }
};

int main(){
    //访问:通过对象访问
    Person p1;
    p1.func();

    //访问:通过类名访问(推荐)
    Person::func();
    return EXIT_SUCCESS;
}

单例

#include<iostream>

class Foo {
private:
    Foo(){};
    Foo(const Foo & foo){} //防止通过拷贝构造创建出新的对象
    static Foo *instance; //设为私有的--防止被修改

public:
    static Foo* getInstance() {
        return instance;
    }
};

Foo * Foo::instance = new Foo; //通过指定Foo作用域,即可视为类内部访问,故可以调用构造器

int main() {
    Foo* bar1 = Foo::getInstance();
    Foo* bar2 = Foo::getInstance();
    std::cout << (bar1 == bar2) << std::endl; //1
    return EXIT_SUCCESS;
}

对象的大小

空对象大小
#include <iostream>
using namespace std;

class Person{};

void test() {
    Person p1;
    /**
     * sizeof = 1
     * 结果:空对象的字节大小为1
     * 原因:所有的对象都有一个独一无二的内存地址,所以也会给空对象分配一个1字节的内存地址
     */
    cout << "sizeof = " << sizeof(p1) << endl;
}

int main() {
    test();
    return EXIT_SUCCESS;
}
非空对象大小
#include <iostream>
using namespace std;

class Person{
    int age;
    
    static string name; //静态成员变量属于类,不属于对象
    
    static void func1() {
        //静态成员函数也属于类,不属于对象
    }

    void func2() {
        //成员函数并不属于类对象,和对象是分开存储的
    }
};

void test() {
    Person p1;
    /**
     * sizeof = 4
     * 结果:字节大小为4(注意和结构体一样存在对齐填充)
     * 原因:此时对象中已有数据,无需特意去标识对象位置,直接使用数据地址即可
     */
    cout << "sizeof = " << sizeof(p1) << endl;
}

int main() {
    test();
    return EXIT_SUCCESS;
}

this指针

概述

c++中(非静态)成员函数,只会产生一份函数实例,所有的对象公用这份实例代码

c++通过特殊的指针this来指向被调用的成员函数所属的对象,以此得知此时成员函数中的成员变量是属于哪个对象的

this指针隐式地存在每个(非静态)成员函数中(第一个隐藏参数)

静态成员函数内部没有this指针,也因此在其内部中无法直接操作非静态成员

#include <iostream>
using namespace std;

class Person{
public:
    int age;
    Person(int age){
        this->age = age;
    }
};

int main() {
    Person p1(18);
    cout << p1.age << endl; //18
    return EXIT_SUCCESS;
}
链式编程
Person& func(Person & p) {
    //...
    return *this;
}

//注意如果返回的是值,得到的将是拷贝构造创建出来的新对象
Person func(Person & p) {
	//...
	return *this;
}
空指针访问成员函数
#include <iostream>
using namespace std;

class Person{
private:
    int age = 10;
public:
    void showClassName(){
        cout << "class name is Person" << endl;
    }
    void showAge(){
        cout << "age is " << this->age << endl;
    }
};

int main(){
    Person* p = NULL;
	//p->showAge(); //报错,this为NULL,没有age属性
    p->showClassName(); //没有使用到this,可以正常访问
    return EXIT_SUCCESS;
}
常函数与常对象
常函数
class Person{
private:
    int age = 10;
public:
    void showAge() const //3. 这个const意味着:const Person * const this,表示常函数,此时无法修改this指向的值了
    {
        /**
         * 0.this的本质:Person * const this
         *    1. 指向无法修改
         *    2. 指向的值可以修改
         */
        //1. this = NULL; //报错,this指向无法修改
        //2. 这里只是展示年龄,如何禁止修改年龄?也就是如何禁止修改this指向的值呢?
        cout << this->age << endl;
    }
};
常对象
class Person{
public:
    int age = 10;
    void func1(){}
    void func2() const{}
};

int main(){
    const Person p;
    //p.age = 30; //报错,加上const后表示常函数,无法对值进行修改
    //p.func1(); //报错,无法访问非常函数,因为在普通函数func1中是可以修改age的,这与常对象相悖
    p.func2(); //可正常访问
    return EXIT_SUCCESS;
}
mutable关键字
class Person{
public:
    mutable int age = 10;
    void showAge() const {
        age = 20; //使用mutable关键字修饰后,可以进行修改
        cout << this->age << endl;
    }
};

int main(){
    const Person p;
    p.age = 30; //使用mutable关键字修饰后,可以进行修改
    return EXIT_SUCCESS;
}

友元

概述

类的主要特点之一就是数据隐藏,即类的私有成员无法在类的外部访问,但是有时候又需要在外部进行访问,这时候就需要通过友元函数来实现了

友元函数是一种特权函数,c++允许这个特权函数访问私有成员

可以将一个全局函数、类中的某个成员函数、甚至整个类声明为友元

友元 - 全局函数
#include <iostream>
#include <string>

using namespace std;

class Room {
    friend void myFriend(Room &room); //将需要访问私有成员的全局函数的声明放入类中,并用friend关键字修饰

public:
    Room() {
        this->settingRoom = "客厅";
        this->bedRoom = "卧室";
    }

    string settingRoom;
private:
    string bedRoom;
};

void myFriend(Room &room) {
    cout << room.settingRoom << endl;
    cout << room.bedRoom << endl;
}

void test_friend() {
    Room *room = new Room();
    myFriend(*room);
}

int main() {
    test_friend();
    return EXIT_SUCCESS;
}
友元 - 类
#include <iostream>
#include <string>

using namespace std;

/*先声明,防止紧接着的MyFriend报错*/
class Room;

/*朋友类*/
class MyFriend {
public:
    MyFriend(); //类内定义,类外去实现
    void visit();
    Room *room;
};

/*房间类*/
class Room {
    friend class MyFriend;
public:
    Room();
    string settingRoom;
private:
    string bedRoom;
};

/*房间类类外实现*/
Room::Room() {
    this->settingRoom = "客厅";
    this->bedRoom = "卧室";
}

MyFriend::MyFriend() {
    this->room = new Room();
}

void MyFriend::visit() {
    cout << room->settingRoom << endl;
    cout << room->bedRoom << endl;
}

void test_friend2() {
    MyFriend myFriend;
    myFriend.visit();
}

int main() {
    test_friend2();
    return EXIT_SUCCESS;
}
友元 - 成员函数
class Room {
    friend void MyFriend::visit();
    //...
}
友元 - 全局函数的类内实现
class Room {
public:
	string m_Desc;
	Room(string desc) {
		this->m_Desc = desc;
	}
    friend void descript() {
    	cout << m_Desc << endl;
    }
}
void test() {
	Room room("this is a room.");
	descript();
}

继承(派生)

示例代码
#include <iostream>
#include <string>

using namespace std;

class Person {
protected:
    string m_Name;
    int m_Age;
public:
    Person(string name, int age) : m_Name(std::move(name)), m_Age(age) {}

    void info() {
        cout << "name is " << this->m_Name << ", age is " << this->m_Age << endl;
    }
};

/**
 * : 表示继承
 * public 表示继承方式
 */
class Teacher : public Person {
private:
    string profession = "teacher";
public:
    Teacher(const string &name, int age) : Person(name, age) { //调用父类的构造
        //...
    }
    void job() {
        cout << "my job is a " << this->profession << endl;
    }
};

void test() {
    Teacher teacher("张三", 12);
    teacher.info();
    teacher.job();
}

int main() {
    test();
    return EXIT_SUCCESS;
}
继承方式

继承方式共有三种:publicprotectedprivate,可以理解为继承之后原父类成员集成到子类中后所属的访问权限

父类的私有属性也会被继承,只不过被编译器隐藏了,无法访问到

所有构造函数、拷贝构造、析构函数、operator=,这些都不会被继承

  • 父类
    class Father {
    public:
    	int a;
    protected:
    	int b;
    private:
    	int c;
    }
    
  • public
    class Child: public Father {
    public:
    	int a;
    protected:
    	int b;
    }
    
  • protected
    class Child: protected Father {
    protected:
    	int a;
    protected:
    	int b;
    }
    
  • private
    class Child: private Father {
    private:
    	int a;
    private:
    	int b;
    }
    
  • 私有属性实质也继承了
    class Child: private Father {
    private:
    	int d;
    }
    //...
    cout << sizeof(Child) << endl; //16 (a + b + c + d)
    //...
    
执行顺序

构造函数执行顺序:父类(无参)构造、成员属性类构造、自身构造

析构函数执行顺序:根据构造的顺序先入后出

#include <iostream>
#include <string>

using namespace std;

class Father {
public:
    Father() {
        cout << "父类构造函数" << endl;
    }
    ~Father() {
        cout << "父类析构函数" << endl;
    }
};

class Other {
public:
    Other() {
        cout << "成员类构造函数" << endl;
    }
    ~Other() {
        cout << "成员类析构函数" << endl;
    }
};

class Son: public Father {
public:
    Son() {
        cout << "子类构造函数" << endl;
    }
    ~Son() {
        cout << "子类析构函数" << endl;
    }
private:
    Other other;
};


void test() {
    Son son;
    /**
     * 父类构造函数
     * 成员类构造函数
     * 子类构造函数
     * 子类析构函数
     * 成员类析构函数
     * 父类析构函数
     */
}

int main() {
    test();
    return EXIT_SUCCESS;
}
同名处理
普通成员
#include <iostream>

using namespace std;

/**
 * 1.子类父类同名函数,优先用子类函数
 * 2.子类含有父类同名函数后,会隐藏掉所有父类的该名函数,包括重载函数
 * 3.想要继续访问父类同名函数,需要用作用域的方式显示调用
 */

class Father {
protected:
    int m_Age = 31;
public:
    void info() {
        cout << "father age is " << this->m_Age << endl;
    }

    void info(int age) {
        cout << "father age is " << age << endl;
    }
};

class Son : public Father {
private:
    int m_Age = 1;
public:
    void info() {
        cout << "son age is " << this->m_Age << endl;
    }
};

void test() {
    Son son;
    son.info();
    //son.info(32); //报错,无法调用
    son.Father::info();
    son.Father::info(32);
}

int main() {
    test();
    return EXIT_SUCCESS;
}
静态成员
#include <iostream>

using namespace std;

/**
 * 1.所有结论通普通成员
 * 2.对于静态成员,除了使用对象方式访问,还可以直接用类进行访问
 */

class Father {
public:
    static int m_Age;
    static void info() {
        cout << "father age is " << m_Age << endl;
    }

    static void info(int age) {
        cout << "father age is " << age << endl;
    }
};
int Father::m_Age = 31;

class Son : public Father {
public:
    static int m_Age;
    static void info() {
        cout << "son age is " << m_Age << endl;
    }
};
int Son::m_Age = 1;

void test() {
    cout << Son::m_Age << endl; //1
    Son::info(); //son age is 1
    Father::info(); //father age is 31
    Son::Father::info(); //father age is 31
    Son::Father::info(32); //father age is 32
}

int main() {
    test();
    return EXIT_SUCCESS;
}
多继承
基本语法
class: public, public{ //... }
菱形继承与虚继承
  • 菱形继承

    如:驴和马都继承了动物类,骡子又同时继承了驴和马两个类。

    这样的继承关系就称为菱形继承。

  • 菱形继承的问题
  1. 二义性

    驴和马都有age属性,导致骡调用时不得不指定作用域,否则存在二义性导致报错

  2. 数据浪费

    骡子从驴和马中分别继承了一份age数据,这显然是浪费且无必要的

  • 虚继承

    解决菱形继承中存在的问题

    #include <iostream>
    
    using namespace std;
    
    /*
     * 使用virtual关键字后
     * 1.Animal称为虚基类
     * 2.Horse和Donkey不在直接继承Animal的age属性,而是存着一个名为的vbptr(virtual base pointer)虚基类指针
     * 3.vbptr指针指向vbtable通过vbtable可以找到对应类(如Horse)的(到age的)偏移量,通过这个偏移量可以定位到Animal的age
     * 4.可知现在Horse、Donkey、Mule都用的一个age(Animal中的age)
     * 5.Mule可以直接调用age属性
     */
    
    /*动物类*/
    class Animal{
    public:
        int age;
    };
    /*马类*/
    class Horse: virtual public Animal {};
    /*驴类*/
    class Donkey: virtual public Animal {};
    /*骡类*/
    class Mule: public Horse, public Donkey {};
    
    void test() {
        Mule mule;
        mule.age = 20;
        cout << mule.Horse::age << endl; //20
        cout << mule.Donkey::age << endl; //20
        cout << mule.age << endl; //20
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    
重载 & 重写 & 重定义
重载
  1. 同一个作用域
  2. 参数个数、顺序、或类型不同
  3. 与函数返回值无关
  4. const也可以作为重载条件(do(const int a){}; do(int a){}
重写
  1. 存在继承
  2. 子类重新定义父类的virtual函数
  3. 函数返回值、名称、参数必须和父类的虚函数一致
  4. 如果父类是个常函数,子类不能丢掉const关键字
重定义
  1. 存在继承
  2. 子类重新定义父类的同名非virtual函数

多态

静态多态与动态多态

c++支持编译时多台(静态多态)和运行时多态(动态多态)。

像运算符重载和函数重载就是编译时多态,

而派生类和虚函数的实现则是运行时多态。

静态多态和动态多态的区别就是:函数地址是早绑定(静态联编)还是晚绑定(动态联编)

如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是说地址时早绑定的,称之为静态多态;

如果函数的调用地址需要在运行时才能确定,这就是晚绑定,也就是动态多态。

一般而言,所谓多态,指的是动态多态。

虚函数
  • 静态联编
    #include <iostream>
    
    using namespace std;
    
    class Animal{
    public:
        void say() {
            cout << "Animal is saying ..." << endl;
        }
    };
    
    class Cat : public Animal {
    public:
        void say() {
            cout << "Cat is saying ..." << endl;
        }
    };
    
    void whoIsSpeaking(Animal& animal) {
    	/*
    	 * 结果:Animal is saying ...
    	 * 结论:默认为静态联编,即编译器就已经将此函数(say)的地址确定为Animal的say的地址
    	 */
        animal.say();
    }
    
    void test() {
        Cat cat;
        whoIsSpeaking(cat);
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    
  • 动态联编
    #include <iostream>
    
    using namespace std;
    
    class Animal{
    public:
        virtual void say() { //使用virtual关键字修饰,称为虚函数
            cout << "Animal is saying ..." << endl;
        }
    };
    
    class Cat : public Animal {
    public:
        void say() { //子类对于virtual关键字,可加可不加
            cout << "Cat is saying ..." << endl;
        }
    };
    
    void whoIsSpeaking(Animal& animal) {
        /*
         * 结果:Cat is saying ...
         * 结论:虚函数属于动态联编,在运行时才确定函数的地址
         */
        animal.say();
    }
    
    void test() {
        Cat cat;
        whoIsSpeaking(cat);
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    
  • 原理
    class Animal1 {
    public:
        void speak() {}
    };
    
    class Animal2 {
    public:
        virtual void speak() {}
    };
    
    void test() {
        cout << "Animal1: " << sizeof(Animal1) << endl; //空对象:1
        cout << "Animal2: " << sizeof(Animal2) << endl; //8
    }
    

    当使用了virtual关键字创建虚函数后,该类就会产生一个vfptr,即虚函数(表)指针

    vfptr指向vftable虚函数表

    vftable中存储着该类该函数的函数地址

    当子类继承了父类时,就会继承父类的vfptrvftable

    如果子类重写了父类的该函数,则子类中的vftable中的原本父类的函数的地址就会被子类自身的该函数地址覆盖

    Animal *animal = new Cat();,只是用父类类型指针去接收,本质还是子类对象,所以运行时就会找到子类的vftable中的函数地址并进行调用

纯虚函数与抽象类
class Abstract {
	virtual 返回值 函数名(形参列表) = 0; //纯虚函数
}

如果一个类包含了纯虚函数,那么该类无法被实例化,并称该类为抽象类

抽象类的子类必须重写父类的纯虚函数,否则也是个抽象类

虚析构与纯虚析构
  • 多态下默认不会调用子类的析构函数
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class Animal{
    public:
        char* m_Name;
    
        Animal() {
            cout << "Animal 无参构造" << endl;
        }
    
        ~Animal() {
            cout << "Animal 析构函数" << endl;
        }
    };
    
    class Cat: public Animal{
    public:
        Cat() {
            cout << "Cat 无参构造" << endl;
        }
    
        Cat(const char* name) {
            cout << "Cat 有参构造" << endl;
            this->m_Name = new char[strlen(name)];
            strcpy(this->m_Name, name);
        }
    
        ~Cat() {
            cout << "Cat 析构函数" << endl;
            if (this->m_Name != nullptr) {
                delete [] this->m_Name;
                this->m_Name = nullptr;
            }
        }
    };
    
    void test() {
        /*
         * Animal 无参构造
         * Cat 有参构造
         * Animal 析构函数
         */
        Animal *animal = new Cat("汤姆");
        delete animal; //堆中创建的需要手动调用delete去释放
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    
  • 使用虚析构
    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class Animal{
    public:
        char* m_Name;
    
        Animal() {
            cout << "Animal 无参构造" << endl;
        }
    
        virtual ~Animal() { //虚析构
            cout << "Animal 析构函数" << endl;
        }
    };
    
    class Cat: public Animal{
    public:
        Cat() {
            cout << "Cat 无参构造" << endl;
        }
    
        Cat(const char* name) {
            cout << "Cat 有参构造" << endl;
            this->m_Name = new char[strlen(name)];
            strcpy(this->m_Name, name);
        }
    
        ~Cat() {
            cout << "Cat 析构函数" << endl;
            if (this->m_Name != nullptr) {
                delete [] this->m_Name;
                this->m_Name = nullptr;
            }
        }
    };
    
    void test() {
        /*
         * Animal 无参构造
         * Cat 有参构造
         * Cat 析构函数
         * Animal 析构函数
         */
        Animal *animal = new Cat("汤姆");
        delete animal; //堆中创建的需要手动调用delete去释放
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    
  • 使用纯虚析构

    纯虚析构需要有声明,也要有实现,且类内声明,类外实现

    如果一个类含所有纯虚析构,它也属于抽象类,无法实例化

    #include <iostream>
    #include <cstring>
    using namespace std;
    
    class Animal{
    public:
        char* m_Name;
    
        Animal() {
            cout << "Animal 无参构造" << endl;
        }
    
        virtual ~Animal() = 0; //纯虚析构
    };
    
    Animal::~Animal() {
        cout << "Animal 析构函数" << endl;
    }
    
    class Cat: public Animal{
    public:
        Cat() {
            cout << "Cat 无参构造" << endl;
        }
    
        Cat(const char* name) {
            cout << "Cat 有参构造" << endl;
            this->m_Name = new char[strlen(name)];
            strcpy(this->m_Name, name);
        }
    
        ~Cat() {
            cout << "Cat 析构函数" << endl;
            if (this->m_Name != nullptr) {
                delete [] this->m_Name;
                this->m_Name = nullptr;
            }
        }
    };
    
    void test() {
        /*
         * Animal 无参构造
         * Cat 有参构造
         * Cat 析构函数
         * Animal 析构函数
         */
        Animal *animal = new Cat("汤姆");
        delete animal; //堆中创建的需要手动调用delete去释放
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    
类型转换
父转子

存在问题,一旦访问只属于子类的成员就会报错

Animal *animal = new Animal;
Cat *cat = (Cat)animal;
子转父

不存在问题,因为父类的私有成员实质上也继承到了子类上,所以子转父后即使访问父类的私有成员也不会报错

Cat *cat = new Cat;
Animal *animal = (Animal)cat;

案例

自定义数组
  • MyArray.h
    #ifndef CPLUSPLUS_MYARRAY_H
    #define CPLUSPLUS_MYARRAY_H
    #include <iostream>
    using namespace std;
    
    class MyArray{
    public:
        MyArray(); //默认构造
        MyArray(int capacity); //有参构造
        MyArray(const MyArray & arr); //拷贝构造
        ~MyArray(); //析构函数
    
        void insert(int val); //尾插
        void setData(int pos, int val); //指定位置插入
        int getData(int pos); //获取指定位置数据
    
        int getCapacity(); //获取容量
        int getSize(); //获取大小
        
    	int &operator[](int index);
    
    private:
        int m_Capacity; //数组容量
        int m_Size; //数组大小
        int * pAddress;//数组指针
    };
    
    #endif //CPLUSPLUS_MYARRAY_H
    
  • MyArray.cpp
    #include "MyArray.h"
    
    MyArray::MyArray() {
        cout << "默认构造" << endl;
        new(this) MyArray(100);
    }
    
    MyArray::MyArray(int capacity) {
        cout << "有参构造" << endl;
        this->m_Capacity = capacity;
        this->m_Size = 0;
        this->pAddress = new int[this->m_Capacity];
    }
    
    MyArray::MyArray(const MyArray &arr) {
        cout << "拷贝构造" << endl;
        this->m_Capacity = arr.m_Capacity; //参数arr也是MyArray,所以可以直接访问自由属性
        this->m_Size = arr.m_Size;
        this->pAddress = new int[arr.m_Capacity];
        for (int i = 0; i < arr.m_Size; ++i) {
            this->pAddress[i] = arr.pAddress[i];
        }
    }
    
    MyArray::~MyArray() {
        cout << "析构函数" << endl;
        if (NULL != this->pAddress) {
            delete [] this->pAddress;
            this->pAddress = NULL;
        }
    }
    
    void MyArray::insert(int val) {
        this->pAddress[this->m_Size] = val;
        this->m_Size++;
    }
    
    void MyArray::setData(int pos, int val) {
        this->pAddress[pos] = val;
    }
    
    int MyArray::getData(int pos) {
        return this->pAddress[pos];
    }
    
    int MyArray::getCapacity() {
        return this->m_Capacity;
    }
    
    int MyArray::getSize() {
        return this->m_Size;
    }
    
    int &MyArray::operator[](int index) {
        return this->pAddress[index];
    }
    
  • MyArrayTest.cpp
    #include "MyArray.h"
    
    void test_array(){
        MyArray arr;
        for (int i = 0; i < 10; ++i) {
            arr.insert(i + 1);
        }
        MyArray arr2(arr);
        arr2.setData(0, 1000);
        for (int i = 0; i < arr.getSize(); ++i) {
            cout << "arr[" << i << "] = " << arr.getData(i) << endl;
        }
        cout << endl;
        for (int i = 0; i < arr2.getSize(); ++i) {
            cout << "arr[" << i << "] = " << arr2.getData(i) << endl;
        }
        arr[1] = 10000; //因为返回的是引用,所以可以进行赋值的操作
    	cout << arr[1] << endl;
    }
    
    int main() {
        test_array();
        return EXIT_SUCCESS;
    }
    
自定义字符串
  • MyString.h
    #ifndef CPLUSPLUS_MYSTRING_H
    #define CPLUSPLUS_MYSTRING_H
    
    #include <iostream>
    
    using namespace std;
    
    class MyString {
    public:
        MyString();
    
        MyString(const char *str);
    
        MyString(const MyString &str);
    
        ~MyString();
    
        MyString &operator=(const MyString &str);
    
        MyString &operator=(const char *str);
    
        char &operator[](int index);
    
        MyString operator+(const MyString &str);
    
        MyString operator+(const char *str);
    
    private:
        friend ostream &operator<<(ostream &out, MyString &str);
    
        friend istream &operator>>(istream &in, MyString &str);
    
        char *pString; //维护在堆中开辟的字符数组
        int m_Size; //字符串长度(不包含\0)
    };
    
    #endif //CPLUSPLUS_MYSTRING_H
    
  • MyString.cpp
    #include "MyString.h"
    #include <cstring>
    
    MyString::MyString() {
        new(this) MyString(new char[]{""});
    }
    
    MyString::MyString(const char *str) {
        this->pString = new char[strlen(str) + 1];
        strcpy(this->pString, str);
        this->m_Size = (int) strlen(str);
    }
    
    MyString::MyString(const MyString &str) {
        this->pString = new char(str.m_Size + 1);
        strcpy(this->pString, str.pString);
        this->m_Size = str.m_Size;
    }
    
    MyString::~MyString() {
        if (nullptr != this->pString) {
            delete[] this->pString;
            this->pString = nullptr;
        }
    }
    
    ostream &operator<<(ostream &out, MyString &str) {
        cout << str.pString;
        return out;
    }
    
    istream &operator>>(istream &in, MyString &str) {
        if (NULL != str.pString) {
            delete[] str.pString;
            str.pString = NULL;
        }
        char buf[1024];
        cin >> buf;
        str.pString = new char[strlen(buf) + 1];
        strcpy(str.pString, buf);
        str.m_Size = strlen(buf);
        return in;
    }
    
    MyString &MyString::operator=(const MyString &str) {
        if (NULL != this->pString) {
            delete[] this->pString;
            this->pString = NULL;
        }
        this->pString = new char(str.m_Size + 1);
        strcpy(this->pString, str.pString);
        this->m_Size = str.m_Size;
        return (*this);
    }
    
    MyString &MyString::operator=(const char *str) {
        return this->operator=(MyString(str));
    }
    
    char &MyString::operator[](int index) {
        return this->pString[index];
    }
    
    MyString MyString::operator+(const MyString &str) {
        return this->operator+(str.pString);
    }
    
    MyString MyString::operator+(const char *str) {
        int size = this->m_Size + strlen(str) + 1;
        char *temp = new char[size];
        memset(temp, 0, size);
        strcat(temp, this->pString);
        strcat(temp, str);
        MyString res = temp;
        delete[] temp; //创建在堆中,所以要清掉
        return res;
    }
    
  • MyStringTest
    #include "MyString.h"
    
    void test() {
        MyString str1("abc"); //有参构造
        MyString str2 = "def"; //有参构造
        MyString str3(str1); //拷贝构造
        cout << "重新输入str1的值:" << endl;
        cin >> str1;
        cout << "str1: " << str1 << endl;
        cout << "str2: " << str2 << endl;
        cout << "str3: " << str3 << endl;
    
        MyString str4; //无参构造
        MyString str5; //无参构造
        cout << "str4: " << str4 << endl;
        str4 = "opq";
        str5 = str4;
        cout << "str5: " << str5 << endl;
    
        MyString *str6 = new MyString("xyz");
        (*str6)[0] = 'X';
        cout << "str6: " << (*str6) << endl;
        delete str6;
    
        MyString str7 = str1 + str2;
        MyString str8 = str7 + "XYZ";
        cout << "str7: " << str7 << endl;
        cout << "str8: " << str8 << endl;
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    

运算符重载

概述

c++对运算符提供了重载函数的功能,格式为operator关键字加上运算符,如加号运算符,重载函数为operator+,这些重载函数可以简写为运算符进行运算,如operator+可以简写为+

实现运算符重载的方式可以是全局函数,也可以是成员函数

operator+

  • 成员函数重载
    #include <iostream>
    
    using namespace std;
    
    class Score {
    private:
        int chinese;
        int math;
        int english;
    public:
        Score() {}
    
        Score(int chinese, int math, int english=60) : chinese(chinese), math(math), english(english) {}
    
        Score operator+(Score &score) {
            Score temp;
            temp.chinese = this->chinese + score.chinese;
            temp.math = this->math + score.math;
            temp.english = this->english + score.english;
            return temp;
        }
    
        int getChinese() {
            return this->chinese;
        }
    
        int getMath() {
            return this->math;
        }
    
        int getEnglish() {
            return this->english;
        }
    };
    
    void test(){
        Score score1(100, 90);
        Score score2(90, 80, 80);
        //Score score3 = score1.operator+(score2);
        Score score3 = score1 + score2; //简写形式
        cout << "chinese: " << score3.getChinese() << endl;
        cout << "math: " << score3.getMath() << endl;
        cout << "english: " << score3.getEnglish() << endl;
    }
    
    int main(){
        test();
        return EXIT_SUCCESS;
    }
    
  • 全局函数重载
    #include <iostream>
    
    using namespace std;
    
    class Score {
    private:
        int chinese;
        int math;
        int english;
    public:
        Score() {}
    
        Score(int chinese, int math, int english=60) : chinese(chinese), math(math), english(english) {}
    
        void setChinese(int chinese) {
            this->chinese = chinese;
        }
    
        int getChinese() {
            return this->chinese;
        }
    
        int getMath() {
            return this->math;
        }
    
        void setMath(int math) {
            this->math = math;
        }
    
        int getEnglish() {
            return this->english;
        }
    
        void setEnglish(int english) {
            this->english = english;
        }
    };
    
    Score operator+(Score score1, Score score2){
        Score temp;
        temp.setChinese(score1.getChinese() + score2.getChinese());
        temp.setMath(score1.getMath() + score2.getMath());
        temp.setEnglish(score1.getEnglish() + score2.getEnglish());
        return temp;
    }
    
    void test(){
        Score score1(100, 90);
        Score score2(90, 80, 80);
    //    Score score3 = operator+(score1, score2);
        Score score3 = score1 + score2; //简写形式
        cout << "chinese: " << score3.getChinese() << endl;
        cout << "math: " << score3.getMath() << endl;
        cout << "english: " << score3.getEnglish() << endl;
    }
    
    int main(){
        test();
        return EXIT_SUCCESS;
    }
    

operator<< | operator>>

实现 cout << 类,即直接打印一个类

  • 成员函数重载
    Class Person{
    public:
    	/*
    	 * 类比 p1.operator+(p2) => p1 + p2
    	 * 得到 p1.operator<<(cout) => p1 << cout
    	 * 所以 这不是想要的效果(cout << p1),故而应采用全局函数方式
    	 */
    	ostream& operator<<(Person & p) {}
    }
    
  • 全局函数重载
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    class Person {
        friend ostream &operator<<(ostream &out, Person &p);
    
    private:
        string m_Name;
        int m_Age;
    public:
        Person(string name, int age) {
            this->m_Name = name;
            this->m_Age = age;
        }
    };
    
    ostream &operator<<(ostream &out, Person &p) {
        out << "name=" << p.m_Name << "; age=" << p.m_Age; //name=张三; age=12
        return out;
    }
    
    void test() {
        Person p("张三", 12);
        cout << p << endl;
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    

operator++ | operator--

#include <iostream>
using namespace std;

class MyInteger {
    friend ostream& operator<<(ostream& out, MyInteger& integer);
private:
    int number = 0;
public:
    /**
     * 前置++
     * @return 返回自身
     */
    MyInteger& operator++(){
        this->number++;
        return *this;
    }

    /**
     * 后置++,通过占位参数,c++会自动识别为后置++操作
     * @return 返回新对象,这也是前置++效率更高的原因(会通过拷贝构造创建一个临时对象)
     */
    MyInteger operator++(int) & {
        MyInteger temp = *this;
        this->number++;
        return temp;
    }
};

ostream& operator<<(ostream& out, MyInteger& integer) {
    cout << integer.number;
    return cout;
}

void test() {
    MyInteger integer;
    cout << integer << endl; //0

    cout << ++(++integer) << endl; //2
    cout << integer << endl; //2

    integer++;
    cout << integer << endl; //3
}

int main() {
    test();
    return EXIT_SUCCESS;
}

智能指针 & 指针运算符重载

#include<iostream>
using namespace std;

class Person{
private:
    int m_Age;
public:
    Person(int age) {
        cout << "调用了Person的有参构造" << endl;
        this->m_Age = age;
    }
    void showAge() const {
        cout << "年龄为:" << this->m_Age << endl;
    }
    ~Person() {
        cout << "调用了Person的析构函数" << endl;
    }
};

int main() {
    test();
    return EXIT_SUCCESS;
}
智能指针
  • 在堆中创建的对象需要手动去调用delete释放
    void test() {
        Person* p = new Person(18);
        p->showAge();
        (*p).showAge();
        delete p;
    }
    
  • 使用智能指针自动释放
    class SmartPoint{
    private:
        Person* m_Person;
    public:
        SmartPoint(Person* p): m_Person(p){}
        ~SmartPoint() {
            if (NULL != this->m_Person) {
                delete this->m_Person;
            }
        }
    };
    
    void test() {
        /*
         * 由于sp不是在堆中创建的,是在栈中创建的,
         * 所以函数test调用结束后会调用它的析构,而在它的析构中对创建的堆中对象Person进行了释放,
         * 从而实现了自动释放的作用
         */
        SmartPoint sp(new Person(18));
    }
    
指针运算符重载
class SmartPoint{
private:
    Person* m_Person;
public:
    Person* operator->() {
        return this->m_Person;
    }
    Person& operator*() {
        return *(this->m_Person);
    }
    SmartPoint(Person* p): m_Person(p){}
    ~SmartPoint() {
        if (NULL != this->m_Person) {
            delete this->m_Person;
        }
    }
};

void test() {
    /*
     * 由于sp不是在堆中创建的,是在栈中创建的,
     * 所以函数test调用结束后会调用它的析构,而在它的析构中对创建的堆中对象Person进行了释放,
     * 从而实现了自动释放的作用
     */
    SmartPoint sp(new Person(18));
    sp->showAge(); //理论上应该是sp->->,不过编译器进行了优化
    (*sp).showAge();
}

operator=

默认的实现是一个值的浅拷贝操作,这也是Person p1; Person p2; p2 = p1得以实现的原因,但是和拷贝构造一样,如果在析构中对对象进行了释放操作,也会存在重复删除,即后面的删除找不到地址的错误,此时就需要对operator=进行重载了

#include <iostream>
#include <cstring>
using namespace std;

class Person{
    friend void test();
private:
    char* m_Name;
    int m_Age;
public:
    Person(char* name, int age): m_Age(age) {
        this->m_Name = new char[strlen(name) + 1];
        strcpy(this->m_Name, name);
    }

    Person(const Person& p) { //与本案例无关,只为健全代码
        this->m_Name = new char[strlen(p.m_Name) + 1];
        strcpy(this->m_Name, p.m_Name);
        this->m_Age = p.m_Age;
    }

    Person& operator=(const Person& p){
        if(nullptr != this->m_Name) { //先将可能存在的旧数据从堆中删除
            delete [] this->m_Name;
            this->m_Name = nullptr;
        }
        this->m_Name = new char[strlen(p.m_Name) + 1];
        strcpy(this->m_Name, p.m_Name);
        this->m_Age = p.m_Age;
        return *this;
    }

    ~Person() {
        if(nullptr != this->m_Name) {
            delete [] this->m_Name;
            this->m_Name = nullptr;
        }
    }
};

void test() {
    Person p1("张三", 19);//有参构造
    Person p2("李四", 20);//有参构造
    Person p3(p2);//拷贝构造
    Person p4 = p3;//拷贝构造
    Person p5("王五", 21);//有参构造
    p1 = p2 = p3 = p4 = p5;
    cout << "p1: " << p1.m_Name << " - " << p1.m_Age << endl;
    cout << "p2: " << p2.m_Name << " - " << p2.m_Age << endl;
    cout << "p3: " << p3.m_Name << " - " << p3.m_Age << endl;
    cout << "p4: " << p4.m_Name << " - " << p4.m_Age << endl;
    cout << "p5: " << p5.m_Name << " - " << p5.m_Age << endl;
    // p1,p2,p3,p4,p5: 王五 - 21
}

int main() {
    test();
    return EXIT_SUCCESS;
}

operator[]

见自定义数组

关系运算符重载

#include <iostream>
#include <string>

using namespace std;

class Person {
    friend void test();

private:
    string m_Name;
    int m_Age;
public:
    Person(string name, int age) : m_Name(name), m_Age(age) {}

    bool operator==(Person &p) {
        return this->m_Name == p.m_Name && this->m_Age == p.m_Age;
    }

    bool operator!=(Person &p) {
        return !this->operator==(p);
    }
};

void test() {
    Person p1("张三", 20);
    Person p2("李四", 21);
    Person p3(p2);
    cout << (p1 == p2) << endl; //0
    cout << (p2 != p3) << endl; //0
}

int main() {
    test();
    return EXIT_SUCCESS;
}

operator()

#include <iostream>
using namespace std;

class MyPrint{
public:
    void operator()(const string& str) {
        cout << str << endl;
    }
};

void test() {
    MyPrint print;
    print("abc"); //仿函数,本质是个对象,也叫函数对象
    MyPrint()("def");
}

int main() {
    test();
    return EXIT_SUCCESS;
}

不要进行重载的符号

对于&&||,由于我们无法模拟短路特性的逻辑,如果对这两种符号进行重载会导致短路特性失效,最后得到错误的结果,所以最好不要对它们进行重载

模板

即类型参数化 - 泛型编程

函数模板

基本语法
#include <iostream>
using namespace std;

/**
 * 1、使用template关键字定义模板
 * 2、用class关键字也可以,不过一般函数模板用typename,类模板用class
 * 3、必须是紧跟着的函数才能使用typename声明的T
 */
template<typename T>
void mySwap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

void test() {
    int a = 10;
    int b = 20;

    /* 
     * 方式一:自动类型推导
     * 注意:类型必须一致
     */
    //mySwap(a, b);

    /* 
     * 方式二:显示指定类型
     * 注意:模板函数的参数需为&(引用),否则传入char类型也可以,因为它可以被隐式转为int类型
     */
    mySwap<int>(a, b);
    cout << "a=" << a << "; b=" << b <<endl;
}

int main() {
    test();
    return EXIT_SUCCESS;
}
模板函数也可以重载
template<typename T>
T test(T a, T b) {
	//...
}

template<typename T>
T test(T a, T b, Tc) {
	//...
}
模板函数和普通函数同时可被调用时
  • 优先调用普通函数

    如果同时可以被模板函数和普通函数调用,即使普通函数只有声明、没有定义

    template<typename T>
    void test(T a, T b) {
    	cout << "模板函数调用" << endl;
    }
    
    void test(int a, int b) {
    	cout << "普通函数调用" << endl;
    }
    
    test(1, 2); //"普通函数调用"
    
  • 强制调用模板函数

    可以使用空模板参数列表来强制转为调用模板函数

    template<typename T>
    void test(T a, T b) {
    	cout << "模板函数调用" << endl;
    }
    
    void test(int a, int b) {
    	cout << "普通函数调用" << endl;
    }
    
    test<>(1, 2); //"模板函数调用"
    
  • 如果函数模板有更好的匹配则优先用函数模板
    template<typename T>
    void test(T a, T b) {
        cout << "模板函数调用" << endl;
    }
    
    void test(int a, int b) {
        cout << "普通函数调用" << endl;
    }
    
    char a = 'a';
    char b = 'b';
    test(a, b); //模板函数调用
    
函数模板具体化

如一个进行两个数据==号操作的模板函数,对于自定义类型是无法实现的,此时可以通过具体化技术,为自定义类型单独写一个特殊的函数模板

#include <iostream>
using namespace std;

class Person {
public:
    string m_Name;
    int m_Age;
    Person(string name, int age): m_Name(name), m_Age(age) {}
};

template<typename T>
bool compare(T &a, T &b) {
    cout << "函数模板调用" << endl;
    return a == b;
}

/*
 * 具体化技术:在函数前添加template<>
 * 具体化技术:可以省略模板声明
 */
template<> bool compare(Person &a, Person &b) {
    cout << "具体化技术" << endl;
    return a.m_Name == b.m_Name && a.m_Age == b.m_Age;
}

void test() {
    Person p1("小明", 19);
    Person p2("小明", 19);
    bool res = compare(p1, p2); //具体化技术,会自动调用对应的特殊模板
    cout << "res = " << res << endl; //1
}

int main() {
    test();
    return EXIT_SUCCESS;
}

类模板

基本语法
#include <iostream>
#include <string>

using namespace std;

/**
 * 1、模板中可以同时声明多个关键字,如这的N和A(函数模板也可以)
 * 2、类模板可以设置默认类型值,如这的A = int(函数模板不可以)
 * 3、类模板不可以使用自动类型推导,必须显示指定类型
 */
template<class N, class A = int>
class Person {
public:
    Person(N name, A age) {
        this->m_Name = name;
        this->m_Age = age;
    }

    N m_Name;
    A m_Age;

    void show() {
        cout << "name=" << this->m_Name << ", age=" << this->m_Age << endl;
    }
};

void test() {
	//Person p("", 1); //报错,必须显示指定类型
    Person<string> p1("张三", 21); //由于设置了默认值,所以可以进行省略
    Person<string, string> p2("孙悟空", "1000");
    p1.show();
    p2.show();
}

int main() {
    test();
    return EXIT_SUCCESS;
}
类模板做函数参数
一、指定参数类型
void showInfo(Person<string, int> &p) {
    p.show();
}

void test() {
    Person<string, int> p("孙悟空", 1000);
    showInfo(p);
}
二、参数模板化
template<typename T1, typename T2>
void showInfo(Person<T1, T2> &p) {
    p.show();
}

void test() {
    Person<string, int> p("孙悟空", 1000);
    showInfo(p);
}
三、类模板化
template<typename T>
void showInfo(T &p) {
    p.show();
}

void test() {
    Person<string, int> p("孙悟空", 1000);
    showInfo(p);
}
类模板与继承
一、指明父类泛型

必须指明类型,否则无法给子类分配内存(即不知道要分配多少内存)

template<class T>
class Father {
public:
    T m; //即使这里没有用到T,子类也要指定泛型
};

class Son: public Father<string> {
    //...
};
二、继续使用泛型
template<class T>
class Father {
public:
    T m_n;
    Father(T n): m_n(n) {}
};

template<class T1, class T2>
class Son: public Father<T1> {
public:
    T2 m_a;
    Son(T1 m, T2 a): Father<T1>(m), m_a(a) {}
};

void test() {
    Son<string, int> son("孙悟空", 10000);
}
类模板成员函数的类外实现
template<class N, class A = int>
class Person {
public:
    Person(N name, A age);

    N m_Name;
    A m_Age;

    void show();
};

/**
 * 1、必须再次声明模板
 * 2、作用域必须指定泛型,即使函数未用到
 */
template<typename T1, typename T2>
Person<T1, T2>::Person(T1 name, T2 age) {
    this->m_Name = name;
    this->m_Age = age;
}

template<typename T1, typename T2>
void Person<T1, T2>::show() {
    cout << "name=" << this->m_Name << ", age=" << this->m_Age << endl;
}
类模板成员函数的分文件实现
  • person.hpp
    #ifndef COURSE_DEMO_HPP
    #define COURSE_DEMO_HPP
    
    #include <iostream>
    #include <string>
    
    using namespace std;
    
    template<class N, class A = int>
    class Person {
    public:
        Person(N name, A age);
    
        N m_Name;
        A m_Age;
    
        void show();
    };
    
    /**
     * 1、必须再次声明模板
     * 2、作用域必须指定泛型,即使函数未用到
     */
    template<typename T1, typename T2>
    Person<T1, T2>::Person(T1 name, T2 age) {
        this->m_Name = name;
        this->m_Age = age;
    }
    
    template<typename T1, typename T2>
    void Person<T1, T2>::show() {
        cout << "name=" << this->m_Name << ", age=" << this->m_Age << endl;
    }
    
    #endif //COURSE_DEMO_HPP
    
  • test.cpp
    #include "demo.hpp"
    
    void test() {
        Person<string, int> p("孙悟空", 100000);
        p.show();
    }
    
    int main() {
        test();
        return EXIT_SUCCESS;
    }
    
  • 说明

    如果将person的成员函数的类外实现放到person.cpp文件中,而自身的声明放入person.h

    由于类模板的代码是在运行期确定的,而test.cpp中引入的是person.h,就会导致无法链接到类外实现的函数而报错

    此时可以让test.cpp直接引入person.cpp,因为这里可以直接找到函数

    但是按业界规范,一般不会去直接引入cpp文件,且一般也不会对类模板的成员函数进行类外实现,而是直接类内实现

    如果一定要类外实现,一般的做法就是都放到一个文件中,然后将这种头文件与具体实现放一起的文件修改为hpp后缀的文件

类模板与友元函数
类内实现
#include <iostream>
#include <string>

using namespace std;

template<class N, class A = int>
class Person {
private:
    N m_Name;
    A m_Age;
public:
    Person(N name, A age) {
        this->m_Name = name;
        this->m_Age = age;
    }

    friend void show(Person<N, A> &p) {
        cout << "name=" << p.m_Name << ", age=" << p.m_Age << endl;
    }
};


void test() {
    Person<string, int> p("孙悟空", 1000);
    show(p);
}

int main() {
    test();
    return EXIT_SUCCESS;
}
类外实现一
#include <iostream>
#include <string>
using namespace std;

//4.需要让3的show知道Person类的存在
template<class N, class A>
class Person;

//3.需要让Person知道函数show的存在
template<typename N, typename A>
void show(Person<N, A> &p);

template<class N, class A = int>
class Person {
private:
    N m_Name;
    A m_Age;
public:
    Person(N name, A age) {
        this->m_Name = name;
        this->m_Age = age;
    }

    //2.通过空模板参数列表<>,告知编译器这是个模板函数,而不是普通函数
    friend void show<>(Person<N, A> &p);
};

//1.定义模板
template<typename N, typename A>
void show(Person<N, A> &p) {
    cout << "name=" << p.m_Name << ", age=" << p.m_Age << endl;
}

void test() {
    Person<string, int> p("孙悟空", 1000);
    show(p);
}

int main() {
    test();
    return EXIT_SUCCESS;
}
类外实现二
#include <iostream>
#include <string>
using namespace std;

//2.需要让1的show知道Person类的存在
template<class N, class A>
class Person;

//1.定义模板
template<typename N, typename A>
void show(Person<N, A> &p) {
    cout << "name=" << p.m_Name << ", age=" << p.m_Age << endl;
}

template<class N, class A = int>
class Person {
private:
    N m_Name;
    A m_Age;
public:
    Person(N name, A age) {
        this->m_Name = name;
        this->m_Age = age;
    }

    //3.通过空模板参数列表<>,告知编译器这是个模板函数,而不是普通函数
    friend void show<>(Person<N, A> &p);
};

void test() {
    Person<string, int> p("孙悟空", 1000);
    show(p);
}

int main() {
    test();
    return EXIT_SUCCESS;
}

类型转换

静态转换

父子类之间指针或引用的转换

多态 - 类型转换

class Base {
};

class Child : public Base {
};

void test() {
    Base *base = nullptr;
    Child *child = nullptr;
    
    //父转子:不安全
    Child *c = static_cast<Child *>(base);
    
    //子转父:安全
    Base *b = static_cast<Base *>(child);
}
基本类型之间的转换
void test() {
    char c = 'a';
    double d = static_cast<double>(c);
    cout << d << endl; //97
}

动态转换

一般用于类层次之间的上下行转换

上行转换:此时动态转换dynamic_cast效果同静态转换static_cast

下行转换:具有类型检查功能,比static_cast更安全

下行转换默认不允许
void test() {
    Base *base = nullptr;
    Child *child = nullptr;

    //父转子:不安全
    //Child *c = dynamic_cast<Child *>(base); //报错
}
如果存在多态则允许下行转换

多态中,类型转换总是安全的

class Base {
    virtual void func() {}
};

class Child : public Base {
    void func() {};
};

void test() {
    /**
     * 多态:
     * 1、继承
     * 2、同名虚函数
     * 4、父类指针指向子类对象
     */
    Base *base = new Child;
    Child *c = dynamic_cast<Child *>(base); //不报错
}

常量转换

常量指针转换成非常量指针
void test() {
    //移除const
    const int* p1 = nullptr;
    int* np1 = const_cast<int*>(p1);
    
    //转为const
    int *p2 = nullptr;
    const int* np2 = const_cast<const int*>(p2);
    
    //禁止对非指针或非引用的变量,使用const_cast转换来直接移除掉它的const
    //const int a = 10;
    //int na = const_cast<int>(a); //报错
}
常量指针转换成非常量引用
void test() {
    int n1 = 10;
    //转为引用
    int & rn1 = n1;
    //非常量引用转为常量引用
    const int &rn2 = const_cast<const int&>(rn1);
}

重新解释转换

关键字:reinterpret_cast

主要用于将一种数据类型转成另一种类型,可以将指针转成一个整数,也可以将一个整数转成指针,是个不安全的转换操作,一般不使用

异常

c++中的异常必须处理,否则程序会自动调用terminate函数,使得程序运行中断

基本语法

#include <iostream>
using namespace std;

class MyException {
public:
    void printError() {
        cout << "除数为0异常" << endl;
    }
};

int division(int a, int b) {
    if (0 == b) {
        //throw 'c';
        //throw 1.12;
        throw MyException();
    }
    return a / b;
}

void test() {
    try {
        division(10, 0);
    } catch (int) { //捕获int类型的异常
        cout << "int 类型的异常" << endl;
    } catch (double) {
        throw; //继续抛出异常
    } catch (char c) { //可以接收异常值
        cout << "char 类型的异常:" << c << endl;
    } catch (MyException e) {
        e.printError();
    } catch (...) {
        cout << "其他 类型的异常" << endl;
    }
}

int main() {
    try {
        test();
    } catch (double) {
        cout << "double 类型的异常" << endl;
    }
    return EXIT_SUCCESS;
}

栈解旋

指在throw抛出异常之前,会将其所处的方法栈中的(非堆)对象进行清除,这个操作称之为栈解旋

#include <iostream>
#include <string>
using namespace std;

class MyException {
public:
    void printError() {
        cout << "除数为0异常" << endl;
    }
};

class Demo {
private:
    string m_Flag;
public:
    Demo(string flag): m_Flag(flag) {
        cout << "Demo的构造函数被调用:" << m_Flag << endl;
    }
    ~Demo() {
        cout << "Demo的析构函数被调用:" << m_Flag << endl;
    }
};

int division(int a, int b) {
    Demo d1("d1");
    if (0 == b) {
        Demo d2("d2");
        throw MyException();
    }
    return a / b;
}

void test() {
    try {
        /**
         * Demo的构造函数被调用:d1
         * Demo的构造函数被调用:d2
         * Demo的析构函数被调用:d2
         * Demo的析构函数被调用:d1
         * 除数为0异常
         */
        division(10, 0);
    } catch (MyException e) {
        e.printError();
    }
}

int main() {
    test();
    return EXIT_SUCCESS;
}

异常接口声明

用于限定函数可能抛出的异常的类型有哪些

class MyException {
public:
    void printError() {
        cout << "除数为0异常" << endl;
    }
};

/**
 * 表示只允许抛出int、MyException类型的异常
 * 如果为空,即throw(),表示不允许抛出异常
 */
void func() throw(int, MyException) {
    throw MyException();
}

void test() {
    try {
        func();
    } catch (MyException e) {
        e.printError();
    } catch (...) {
        cout << "other exception !" << endl;
    }
}

异常变量的抛出与接收

class MyException {
public:
    MyException() {
        cout << "MyException的默认构造" << endl;
    }
    MyException(const MyException &e) {
        cout << "MyException的拷贝构造" << endl;
    }
    ~MyException() {
        cout << "MyException的析构函数" << endl;
    }
    void printError() {
        cout << "除数为0异常" << endl;
    }
};
方式一
/**
 * 抛出:MyException()
 * 接收:MyException e
 * 结果:
 *  1、MyException的默认构造
 *  2、MyException的拷贝构造
 *  3、除数为0异常
 *  4、MyException的析构函数
 *  5、MyException的析构函数
 */

void func() {
    throw MyException();
}

void test() {
    try {
        func();
    } catch (MyException e) { //值传递,会调用拷贝构造创建一个新对象
        e.printError();
    }
}
方式二

推荐

/**
 * 抛出:MyException()
 * 接收:MyException &e
 * 结果:
 *  1、MyException的默认构造
 *  2、除数为0异常
 *  3、MyException的析构函数
 */

void func() {
    throw MyException();
}

void test() {
    try {
        func();
    } catch (MyException &e) { //通过引用传递,不会创建新对象,效率会高些
        e.printError();
    }
}
方式三
/**
 * 抛出:&MyException()
 * 接收:MyException *e
 * 结果:
 *  1、MyException的默认构造
 *  2、MyException的析构函数
 *  3、除数为0异常
 */

void func() {
    throw &MyException();
}

void test() {
    try {
        func();
    } catch (MyException *e) {
        cout << "自定义异常" << endl;
        //e->printError(); 对象被提前释放,无法对其进行调用
    }
}
方式四
/**
 * 抛出:new MyException()
 * 接收:MyException *e
 * 结果:
 *  1、MyException的默认构造
 *  2、除数为0异常
 *  3、MyException的析构函数
 */

void func() {
    throw new MyException();
}

void test() {
    try {
        func();
    } catch (MyException *e) {
        e->printError();
        delete e; //需要手动释放
    }
}

异常与多态

class BaseException{
public:
    virtual void printError() = 0;
};

class NullPointerException: public BaseException {
public:
    void printError() override {
        cout << "空指针异常" << endl;
    }
};

class OutOfRangeException: public BaseException {
public:
    void printError() override {
        cout << "数组下标越界异常" << endl;
    }
};

void func() {
    //throw NullPointerException();
    throw OutOfRangeException();
}

void test() {
    try{
        func();
    }catch(BaseException &e) {
        e.printError();
    }
}

系统标准异常

异常类异常说明
exception所有标准异常类的基类
bad_alloc当 operator new 和 operator new[]请求分配内存失败时
bad_exception特殊的一个异常,如果函数抛出了非 指定的异常抛出列表 中的异常,不论什么类型,都会被替换为bad_exception
bad_typeid使用typeid操作符,操作一个NULL指针,而该指针是带有虚函数的类时
bad_cast使用dynamic_cast转换失败时
ios_base::failureio操作过程出现错误
logic_error逻辑错误,可以在运行前检测的错误
runtime_error运行时错误,仅在运行时才可以检测的错误
logic_error的子类
length_error试图生成一个超出该类型最大长度的对象,如vertor的resize操作
demain_error参数的值域错误,主要发生在数学函数中,例如一个负值去调用了一个只能操作非负数的函数
out_of_range超出有效范围,如数租下标越界
invalid_argument参数不合适,如利用string对象构造bitset时,string中的字符不是'0'或'1'的话,抛出该异常
runtime_error的子类
range_error计算结果超出了有意义的值域范围
overflow_error算术计算上溢
underflow_error算术计算下溢
#include <iostream>
#include <stdexcept> //需要导入异常库

using namespace std;

void func(int n) {
    if (n < 10) {
        throw out_of_range("数值必须大于等于10");
    }
}

void test() {
    try{
        func(1);
    } catch (out_of_range &e) { //一般用多态的方式,即exception去接收
        cout << e.what() << endl;
    }
}

int main() {
    test();
    return EXIT_SUCCESS;
}

扩展系统异常

#include <iostream>
#include <string>

using namespace std;

class MyOutOfRangeException : public exception {
private:
    string m_Message;
public:
    MyOutOfRangeException(char *str) {
        this->m_Message = str; //char *可以直接转为string(反之则不行)
    }

	//重写时const和异常范围不能丢
    const char * what() const _GLIBCXX_TXN_SAFE_DYN _GLIBCXX_NOTHROW {
        return this->m_Message.c_str(); //string转char *
    }
};

void func(int n) {
    if (n < 10) {
        throw MyOutOfRangeException("数值必须大于等于10");
    }
}

void test() {
    try {
        func(1);
    } catch (exception &e) { //一般用多态的方式,即exception去接收
        cout << e.what() << endl;
    }
}

int main() {
    test();
    return EXIT_SUCCESS;
}

IO

继承关系图

在这里插入图片描述

四种流对象

对象含义对应设备对应类c语言中响应的标准文件
cin标准输入流键盘instream_withassignstdin
cout标准输出流屏幕onstream_withassignstdout
cerr标准错误流屏幕onstream_withassignstderr
clog标准日志流屏幕onstream_withassignstderr

标准输入流

get()

等待输入:等待缓冲区中有数据读

void test() {
    /**
	 * 无参:一次只能读取一个字符
	 * 输入:as
	 * 输出:c1=a, c2=s, c3=换行符, c4=等待输入
	 */
    char c1 = cin.get();
    char c2 = cin.get();
    char c3 = cin.get();
    char c4 = cin.get();
    cout << "c1=" << c1 << ", c2=" << c2 << ", c3=" << c3  << ", c4=" << c4 << endl;

    /**
     * 一参:将读取的一个字符放入参数中
     * 输入:a
     * 输出:c=a
     */
    char c;
    cin.get(c);
    cout << "c=" << c << endl;

    /**
     * 两参:将缓冲区数据都放入第一个参数,最多放第二个参数个
     * 注意:换行符不会被放入第一个参数,会遗留在缓冲区中,需要再手动调用一次 cin.get() 来清空缓冲区
     * 输入:hello world
     * 输出:hello w
     */
     char buf1[24] = {0};
     cin.get(buf1, 8);
     cout << "buf1=" << buf1 << endl;

    /**
     * 两参:换行符不会被放入第一个参数,会遗留在缓冲区中,需要再手动调用一次 cin.get() 来清空缓冲区
     * 输入:hello world
     * 输出:
     *      buf2=hello world
     *      true
    */
    char buf2[24] = {0};
    cin.get(buf2, 24);
    cout << "buf2=" << buf2 << endl; //buf2=hello world
    char n = cin.get();
    cout << (n == '\n'? "true" : " false") << endl; //true
}
getline()

用法同get()的两参的情况

它们都不会将换行符放入第一个参数中,

不过不同的是getline()会自动将换行符清理丢弃,而不是遗留在缓冲区中

ignore()

忽略前n个字符,n可指定,不指定则默认为1

void test() {
    /**
     * 无参:默认忽略一个字符
     * 输入:hello
     * 输出:c1=e
     */
    cin.ignore();
    char c1 = cin.get();
    cout << "c1=" << c1 << endl;

    /**
     * 有参:如5,忽略5个字符
     * 输入:hello
     * 输出:c2=换行符
     */
    cin.ignore(5);
    char c2 = cin.get();
    cout << "c2=" << c2 << endl;
}
peek()

从缓冲区中拷贝出数据,而不是直接取走缓冲区的数据

void test() {
    /**
     * 无参:从缓冲区中拷贝出一个字符
     * 输入:hel
     * 输出:c1=e, c2=e, c3=l
     */
    cin.ignore();
    char c1 = cin.peek();
    char c2 = cin.get();
    char c3 = cin.get();
    cout << "c1=" << c1 << ", c2=" << c2 << ", c3=" << c3 << endl;
}
putback()

将字符放回上次获取数据的位置(字符不一定来自缓冲区)

void test() {
    /**
     * 一参:将该参数原位置放回缓冲区
     * 输入:hello
     * 输出:buf1=hello
     */
    char c = cin.get();
    cin.putback(c);
    char buf1[1024] = {0};
    cin.getline(buf1, 1024);
    cout << "buf=" << buf1 << endl;

    /**
     * 一参:参数可以是任意的字符,不一定要来自缓冲区
     * 输入:hello
     * 输出:buf1=allo
     */
    cin.ignore();
    cin.get();
    cin.putback('a');
    char buf2[1024] = {0};
    cin.getline(buf2, 1024);
    cout << "buf=" << buf2 << endl;
}
>>

将输入放入变量中

void test() {
    /**
     * 输入:123
     * 输出:123
     */
    char c = cin.peek();
    if (c >= '0' && c <= '9') {
        int num;
        cin >> num;
        cout << num << endl;
    }
}
fail() & clear() & sync()

fail():缓冲区是否异常(异常标识位),0否,1是

clear():清空缓冲区

sync():重置异常标识位为0

标准输出流

flush()

刷新缓冲区(linux下有效)

void test() {
	cout << "hello world";
	cout.flush();
}
put()

向缓冲区中写入字符

void test() {
    //向屏幕中输出hello
    cout.put('h').put('e').put('l').put('l').put('o');
}
write()

向屏幕中输出字符串

第一个参数为要输出的内容,第二个参数为输出的最多字符个数

void test() {
    char buf[] = "hello world";
    cout.write(buf, (int)strlen(buf)); //hello world
}
格式化输出
方式一:通过流成员函数
void test() {
    int number = 99;

    /**
     * 作用:指定宽度为20个字符
     * 输出:                  99(18个空格99)
     */
    cout.width(20);

    /**
     * 作用:指定填充符号
     * 输出:******************99(18个*99)
     */
    cout.fill('*');

    /**
     * 作用:左对齐 - 值放左边(默认放右边)
     * 输出:99******************
     */
    cout.setf(ios::left);

    /**
     * 作用:卸载10进制
     */
    cout.unsetf(ios::dec);

    /**
     * 作用:安装16进制
     * 前提:需要先卸载掉原来的10进制
     * 输出:63******************
     */
    cout.setf(ios::hex);

    /**
     * 作用:显示基数(进制前缀)
     * 输出:0x63****************
     */
    cout.setf(ios::showbase);
    
    /**
     * 作用:卸载16进制
     */
    cout.unsetf(ios::hex);

    /**
     * 作用:安装8进制
     * 输出:0143****************
     */
    cout.setf(ios::oct);
    cout << number << endl; //0143
}
方式二:通过控制符

需要引入头文件:#include<iomanip>

void test() {
    int number = 99;
    cout << setw(20) //同width(20)
         << setfill('*') //同fill('*')
         << setiosflags(ios::left) //同setf(ios::left);
         << hex //同unsetf(ios::dec) + setf(ios::hex)
         << setiosflags(ios::showbase) //同setf(ios::showbase)
         << number
         << endl;
}

文件读写

头文件:#include <fstream>

继承关系图

在这里插入图片描述

文件打开方式
ios::in

以输入方式打开

ios::out

以输出方式打开(默认),文件不存在则自动创建,会覆盖源文件内容

ios::app

以输出方式打开,文件不存在则自动创建,数据以追加形式写入

ios::ate

以输出方式打开,文件不存在则自动创建,文件指针指向文件末尾

ios::trunc

打开一个文件,如果文件已存在则删除文件数据,如果文件不存在则创建该文件

ios::binary

以二进制方式打开文件(默认ASCII方式),文件不存在则自动创建,

写数据到文件
#include <fstream>
void test() {
    //ofstream ofs("./test.txt", ios::out); //直接在构造对象时指定路径和打开方式
    ofstream ofs;
    ofs.open("./test.txt", ios::out|ios::trunc); //可同时制定多个打开方式
    cout << ofs.is_open() << endl;
    if (ofs.is_open()) { //是否成功打开
        ofs << "hello" << endl;
        ofs << "world" << endl;
        ofs.close(); //关闭流
    }
}
读取文件数据
方式一:<<

一次读取一行数据

#include <fstream>
void test() {
    ifstream ifs("./test.txt", ios::in);
    if (ifs.is_open()) {
        char buf[1024] = {0};
        while (ifs >> buf) { //一次读取一行数据
            cout << "当前buf:" << buf << endl;
        }
        ifs.close(); //关闭流
    }
}
方式二:getline

一次读取一行数据

#include <fstream>
void test() {
    ifstream ifs("./test.txt", ios::in);
    if (ifs.is_open()) {
        char buf[1024] = {0};
        while (ifs.getline(buf, sizeof(buf))) { //一次读取一行数据
            cout << "当前buf:" << buf << endl;
        }
        ifs.close(); //关闭流
    }
}
方式三:全局getline

一次读取一行数据

#include <fstream>
void test() {
    ifstream ifs("./test.txt", ios::in);
    if (ifs.is_open()) {
        string buf;
        while (getline(ifs, buf)) { //一次读取一行数据
            cout << "当前buf:" << buf << endl;
        }
        ifs.close(); //关闭流
    }
}
方式四:get

一次读取一行个字符

#include <fstream>
void test() {
    ifstream ifs("./test.txt", ios::in);
    if (ifs.is_open()) {
        char c;
        while ((c = ifs.get()) != EOF) { //一次读取一行数据
            cout << "当前c:" << c << endl;
        }
        ifs.close(); //关闭流
    }
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值