c++基础

C++

一、从c到c++

1.c++简介

c++不是新的语言,是在c基础上作了完善和扩充。

c++是c的一个超集,任何合法的c程序都是合法的c++程序。

(1)结构
#include <iostream>

using namespace std;

int main()
{
    cout<< "c++ program" <<endl;//endl为换行,相当于\n
    return 0;
}

注:命名后缀为.cpp 编译器使用g++

(2)特点:

c++完全支持面向对象的程序设计,包括面向对象的三大特性:封装、继承、多态。

2.c++新增特性

(1)更为严格的类型检查
eg:
int *p;
char *q;
p = q;//char * -> int *;g++报错error,gcc:警告warning
(2)新增变量引用(数据类型)

引用:给变量取别名(用于部分代替指针)

eg:
int a = 123;
int &b = a;//引用只能初始化
printf("%d %d %p %p\n",a,b,&a,&b);

在c++中函数传参:

①赋值传递

②传地址

③引用传参(效率最高)

eg:
    		int add(int a, int b)
    		{
    			return a+b;
    		}
    		int add(int *p, int *q)
    		{
    		
    			return *p+*q;
    		}
    		
    		int fun(int &a, int &b)
    		{
    			return a+b;
    		}
    		
    		int main()
    		{
    			int m = 10;
    			int n = 20;
    			
    			add(m,n);//值传参
    			add(&m, &n);//地址传参
    			
    			fun(m,n);//引用传参
    			fun(123,456);//error 常数不能取别名
    			
    			return 0;
    		}
(3)支持面向对象编程

​ 新增了类类型

​ 类和对象、继承、多态、虚函数及RTTI(运行时类型识别)

(4)新增泛型编程

​ 目的:编写与类型无关的代码)

​ 支持模板(template)、标准模板库(STL)

			eg:
			模板函数:
    		template <class T>
    		T add (T a, T b)
    		{
    			return a+b;
    		}
(5)支持异常处理
(6)支持函数及运算符重载:解决重名问题

函数重载:

​ c++用同一函数名定义多个函数,这些函数功能相近但参数个数、参数类型不同。这就是函数的重载(function overloading)。即对一个函数名重新赋予它新的含义,使一个函数名可以多用。

eg:
int add(int a)
{
    return a+a;
}
int add(int a,int b)
{
    return a+b;
}
char add(char x,char y)
{
    return x+y;
}
int main()
{
    add(10);//int add(int a)				add(10)
    add(123,456);//int add(int a,int b)		add(123,456)
    add('a','b');//char add(char x,char y)	add('a','b')
    return 0;
}
(7)支持名字空间

用于管理函数名、变量名及类

3.OOP简介(抽象)

4.泛型编程简介

5.编译c++程序:预处理、编译、汇编、链接

注:

源文件扩展名: *.cpp

引用头文件:新式风格 例如:#include

编译器:
linux: g++
windows : Mingw 32/64

6.c和c++兼容及差异

1)const变量
c :
 		const int a = 123;//只能初始化
		a = 321; //error只能变量
 C++const int *p;
		int *q;
 		q= p; //error const ->非const转换
2)强制类型形式
c:
		(新类型)表达式
        eg:
		int a;
		char ch = (char) a;
C++:
		新类型(表达式)
        eg:
		const int *p;
		int *q;
		q = (int *) p;//c形式
		q = int * (p); //c++形式
3)reference引用
4)内联函数

inline 返回值类型 函数名(形参列表);
注:
a.语句条数<=5
b.不能使用控制语句,例如循环或switch语句

5)函数重载
6)默认参数
eg:
		int add(int a = 0)
        {
            return a+a;
        }
注意:

​ 若其中一个参数指定了默认值,则此参数后边所有参数都要指定默认值
​ 若给有默认值的参数传递了实参,则形参的值为实参值,否则,形参的值是默认值
​ 函数重载与默认参数一起使用时产生歧义

7)函数模板
8)结构体
 c:
            struct student
            {
                int id;
                char name[20];
                int age;
                float score;
            };
		c++:
			struct Demo
            {
                
             public://公有权限:可以通过结构体变量直接访问
                
                void setval(int value)//成员函数
                {
                    val = value;
                }
                int getvalue()//成员函数
                {
                    return val;
                }
                
              private://私有权限:只能在成员函数中访问不可以通过结构体变量直接访问

                int val;//成员变量
            };
			int main()
            {
                struct Demo a;
                a.val;//error私有成员不能直接访问,只能通过成员函数访问
                a.setval (123);
                cout << a.getval() << endl;|
                return 0;
            }

注意:

​ 结构体权限:
​ public:公有权限:可以通过结构体变量直接访问

​ protected:保护权限:只能在内部或子类内部访问

​ private:私有权限:只能在成员函数中访问不可以通过结构体变量直接访问

​ 注:结构体中默认权限为public权限

二、内存模型与名字空间

1.作用域

(1)局部域

​ 局部变量的作用域

(2)全局域

​ 全局变量的作用域

(3)名字空间域
a.语法:

​ namespace 名字空间名称

​ {

​ 代码

​ 函数

​ 类

​ }

eg:
	//名字空间A
	namespace A
    {
        int i = 123;
        void prnmsg()
        {
            printf("hello world\n");
        }
    }
	//名字空间B
	namespace B
    {
        int i = 123;
        void prnmsg()
        {
            printf("hello world\n");
        }
    }
b.使用
	//声明名字空间域再使用
	using namespace A;
    cout << i <<endl;
    prnmsg();

	//不声明直接使用
	cout<<B::i<<endl;
	B::prnmsg();
(4)类域
eg:
	class Demo
    {
     public:
        void setx(int val)//类域
        {
            x = val;
        }
     private:
        int x;//类域
    };

2.链接性及存储性

(1)变量的存储型和链接性
存储类型作用域链接性声明方式
auto整个模块内模块内
register整个模块内模块内,使用register修饰
全局变量文件外部所有函数之外,没有static
static的全局变量文件内部所有函数之外,没有static
static的局部变量模块内模块内,用static修饰
(2)函数的链接性

​ 非static函数:外部链接
​ static函数: 内部链接

	eg :
        vi fun.c:

        static void fun( )//只能在本文件调用
        {
        	printf ("fun. . . . \n");
        }

(3)语言的链接性
mian.cpp
        
        #include <iostream>

        extern "C" void prnmsg(const char *);//生命prnmsg是一个C函数

        int main()
        {
            prnmsg("AAA");
            return 0;
        }

  prgmsg.c
      
      #include <stdio.h>
        void prnmsg(const char *str)
        {
            printf("%s\n", str);
        }

gcc -c prnmsg.c

g++ -c main.cpp

g++ *.o

3.动态内存

​ 在c++中引入new delete代替malloc free

​ 在堆区申请和释放空间(new,delete)

eg:
	int *p = new int;
	*p = 321;
	delete p;


	char *q = new char [16];
	strcpy(q, "he11o world");
	delete [] q;
I

注:

​ 成对出现:new与delete必须成对出现

​ 如果new的是数组,需delete [] 释放

eg:
	 	int *q = new int[10]; //在堆区申请一片空间 大小:sizeof(int) * 10
		delete[] q;

4.声明区与作用域

注:访问全局变量方法,使用运算符“::”

#include <iostream>
using namespace std;

int a = 123;
int main()
{
    int a = 0;
    cout << a << endl;
    cout << ::a << endl; //访问全局变量
}

三、标准流

1.概述

2.c++输出

3.c++输入

4.格式控制符

四、类和对象

1.oop的特征

(1)封装:将对象共有的属性和行为封装成一个整体,实现模块化

​ 封装性实际上由编译器去识别关键字public、private、protected来实现,体现在类的成员可以有公有成员(public)、私有成员(private)、保护成员(protected)。

​ 私有成员是在封装体内被隐藏的部分,只有类内部说明的函数(类的成员函数)才可以访问私有成员,而在类体外的函数是不能访问的(友元除外);

​ 公有成员是封装体与外界的一个接口,类外的函数可以访问公有成员;

​ 保护成员是只有该类的成员函数和该类的派生类才可以访问。

​ 省略权限:默认private

(2)继承:子类继承父类的属性和行为,实现代码复用

​ 被继承的是父类(基类),继承出来的是子类(派生类),子类拥有父类的所有特性。

(3)多态:子类继承父类的成员函数,并且可以对父类的接口进行修改,实现接口重用

​ 多态性是指对不同类的对象发出相同的消息会有不同的实现。

​ c++有两种多态,称为动态多态(运行期多态)和静态多态(编译器多态)。

​ 静多态主要是通过模板来实现,而动多态则是通过虚函数来实现的。

​ 在基类中存在虚函数(一般是纯虚函数),子类通过重载这些接口,使用基类的指针或引用指向子类的对象,就可以调用子类对应的函数。

​ 动多态的函数调用机制是执行期才能确定的,所以它是动态的。

2.类和对象

(1)类

​ 是一种自定义类型,描述一类事物共有的属性和行为

​ 是对象的类型

​ 类是对象的抽象,对象是类的实体;

(2)对象

​ 某一个类的具体实例,用抽象数据类型定义的变量

注:

​ 类是抽象的,不占用内存;

​ 对象是具体的(类型),占用存储空间(类型);

​ 一切皆对象

	int i,j;	//i,j都是int类的具体实例
	Point obj;	//obj是Point类的具体实例

3.类的语法

class 类名(首字母大写)

{

public://公有成员
成员属性和成员方法;

protected://保护成员
成员属性和成员方法;

private://私有成
成员属性和成员方法;

};

eg:
	class Demo
    {
        public://	成员函数、方法
        	void setval(int val)
            {
                myval = val;
            }
        	int getval()
            {
                return myval;
            }
        
        protected:
        
        private://	成员变量、属性
        	int myval;
    };
	int main()
    {
        Demo obj;//定义对象obj
        obj.setval(666);
        cout<<obj.getval()<<endl;
        
        return 0;
    }

五、构造函数与析构函数

1.构造函数:

​ 定义对象时,强制执行的函数(用于初始化工作)

(1)功能:

​ 初始化对象

(2)特点:

​ ①函数名与类名相同,无返回值,构造对象时自动调用,不可手动调用,可以重载

​ ②如果没有,编译器会自动生成一个

2.析构函数:

​ 销毁对象时,强制执行的函数(用于清理工作)

(1)功能:

​ 用于清理功能

(2)特点:

​ ①无返回值,无参数,函数名与类名相同且加上~

​ ②如果没有,编译器会自动生成一个

​ ③对象回收时做回收清理工作


eg:
class Demo
{
public:
	Demo(int val = 0)//构造函数:构造对象时自动调用,不可手动调用,函数名与类名同名,无返回值,可重载
    {
        myval = val;
		cout <<__func__ <<":" <<__LINE__ <<endl;
    }
    ~Demo()//析构函数:回收清理	特点∶函数名是~类名 无返回值 无参数 不可重载 对象结束时自动结束 可手动调用

    {
        cout <<__func__ <<":" <<__LINE__ <<endl;
    }
public:
	void setval(int val)
    {
		myval = val;
    }
	int getval()
    {
		return myval;
    }
private:
	int myval;
};
int main()
{
    Demo obj(666); //定义对象obj 且给构造函数传参666

    cout << "obj.myval = " << obj.getval() << endl;

    Demo *p = new Demo(123);
    cout << "p.myval = " << p->getval() << endl;
    delete p;

    return 0;
}

3.构造和析构顺序

​ 构造顺序:先定义的先析构

​ 析构顺序:根据生命周期具体分析,同一生命周期,先构造的后析构,后构造的先析构

4.拷贝构造

​ 拷贝构造函数:当一个对象初始化另一个对象时调用拷贝构造。

​ 每一个类都有一个拷贝构造函数,若类中定义了则使用定义的拷贝构造函数,否则使用系统提供默认的拷贝构造函数。

5.深拷贝与浅拷贝

​ 浅拷贝:只拷贝值

​ 深拷贝:不仅拷贝值,还要拷贝一份空间

注:

​ 系统提供的拷贝构造函数是浅拷贝

eg:

class Array
{
 public:
    Array(int len)//构造函数
    {
        this->len = len;
        ptr = new int [len];
        assert(ptr != NULL);//断言函数,表达式为真继续执行,表达式为假结束程序,头文件#include <assert.h>
        for(int i = 0 ;i< len ;i++)
            ptr[i] = 0;
    }
    
    Array(const Array &obj)//拷贝构造
    {
        this->len = obj.len;
    #if 0//浅拷贝:只拷贝值
        this->ptr = obj.ptr;
    #else//深拷贝:拷贝空间
        this->ptr = new Array [len];
        assert(ptr != NULL);
    #endif
        for(int i = 0 ;i< len ;i++)
            this->ptr[i] = obj.ptr[i];
    }
    
    ~Array()//析构函数
    {
        delete [] ptr;
    }
    
    void setval(int pos,int val)//设置pos位置的值为val
    {
        ptr[pos] = val;
    }
    
    int getval(int pos)//获取pos位置的值
    {
        return ptr[pos];
    }
    
 private:
    int *ptr;//数组首地址
    int len;
};
int main()
{
    Array a(10);
    for(int i = 0 ;i< 10 ;i++)
    	a.setval(i,i+1);
    
    for(int i = 0 ;i< 10 ;i++)
    	cout<<a.getval(i)<<" ";
    cout<<endl;
    
}

6.系统默认提供

​ (1)构造函数

​ (2)拷贝构造函数

​ (3)析构函数

​ (4)‘=’赋值运算符

​ (5)‘&’取地址运算符

7.默认参数

​ 定义类的方法成员时可以给成员函数参数指定默认值;

​ 注:

​ ①若给参数传递实参,则参数的值为实参的值,否则参数的值就是默认值

​ ②若其中一个参数有默认参数值,则此参数后边所有的参数都必须有默认参数值

六、this指针

​ 定义:对象内部的指针,只能在类内部使用,this指针是所有成员函数的隐含参数

​ this指向对象,可省略

​ 每个对象都有this指针

七、static关键字

1.c中static的用法

​ (1)static修饰局部变量:修改了局部变量的生命周期

​ 将局部变量的生命周期扩展到整个程序结束,该局部变量只能被定义一次

​ (2)static修饰全局变量:修改了全局变量的作用域

​ 该全局变量不能被源程序中其他文件引用

​ (3)static修饰函数(静态函数):修改了函数的作用域

​ ①这个函数只能在本文件中使用

​ ②在源程序中其他文件可以定义相同名字的函数

2.c++中static的用法

​ static修饰成员变量:必须在类外部声明定义初始化

​ static修饰成员方法:使用 类名::函数名(参数) 访问

​ 注:

​ ①static成员是类的成员,与对象无关,及对象不存在,static成员依然存在

​ ②static成员是类对象的其享成员

eg:
    class Demo
    {
     public:
        Demo(int v = 0)
        {
            myval = v;
			cout <<__func__ <<":" <<__LINE__ <<endl;
        }
        ~Demo()
        {
			cout <<__func__ <<":" <<__LINE__ <<endl;
        }
        
        void setmyval(int val)
        {
            this->myval = val;
        }
        int getmyval()
        {
            return this->myval;
        }
        static int getval()//static成员函数,不属于类对象
        {
            return val;
        }
        static int* getaddr()
        {
            return &val;
        }
     private:   
        int myval;
        static int val;//static成员变量
    };
	
	int Demo::val = 123;//static成员必须要在类外声明定义初始化

	int main()
    {
        Demo obj;
        cout << "sizeof(obj) = "<< sizeof(obj) <<endl;
        cout << "Demo->&val = " << Demo::getaddr() << endl;//直接用类名访问
    	cout << "obj->&val = " << obj.getaddr() << endl;	
    }

八、const关键字

1.c中const的用法

(1)修饰指针

​ ①不能通过指针修改其指向的内容

​ const 数据类型 *指针变量

​ 数据类型 const *指针变量

​ ②不能修改指针的指向

​ 数据类型 *const 指针变量

​ ③既不能通过指针修改其指向的内容,也不能修改指针的指向

​ const 数据类型 *const 指针变量

eg:
	//	不能通过指针p修改p指向的内容
	const int *p;
	int const *p;

	//	不能修改p的指向
	int * const p;
	
	//	既不能通过指针p修改p指向的内容,也不能修改p的指向
	const int *const p; 
(2)修饰变量

​ 修饰变量,变量是只读变量,即变量只能初始化,不能被修改

eg:
	const int val = 123;//初始化
	val = 321;//error

2.c++中const的用法

(1)const修饰成员变量

​ ①必须使用初始化列表进行初始化

​ ②const成员是只读的,只能初始化,不能修改

	class Demo
    {
     public:
        Demo(int v = 0,int v1 = 0):myval(v),value(v1)//初始化列表
        {
            myval = v;//赋值
            //value = v1;//赋值 error value是const成员
			cout <<__func__ <<":" <<__LINE__ <<endl;
        }
        ~Demo()
        {
			cout <<__func__ <<":" <<__LINE__ <<endl;
        }
        
        void setmyval(int val)
        {
            this->myval = val;
        }
        int getmyval()
        {
            return this->myval;
        }
    	int getvalue()
        {
            return this->value;
        }
        
     private:   
        int myval;
    	const int value;
        
    };
	
	
	int main()
    {
        Demo obj(666,888);
        cout << "sizeof(obj) = "<< sizeof(obj) <<endl;
        cout << "value = " <<obj.getvalue()<<endl;
        cout << "myval = " <<obj.getmyval()<<endl;
    }
(2)const修饰成员方法

​ 不能通过此方法修改类对象

​ 即:

​ ①const方法只能调用const方法

​ ②const方法不能调用非const方法

​ ③const方法可以修改static成员变量

class Demo
{
public:
    Demo(int v = 0) : myval(v) //初始化列表
    {
        myval = v;
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    void setmyval(int val)
    {
        this->myval = val;
    }

    static int getval()
    {
        return val;
    }

    int getmyval() const; //const修饰成员方法

private:
    int myval;
    static int val;
};

int Demo::val = 123; //static成员必须要在类外声明定义初始化

int Demo::getmyval() const
{
    //myval++;//error const方法不能修改类对象
    val++; //static 不属于类对象,所以可以被const方法修改
    return this->myval;
}

int main()
{
    Demo obj;
    cout << "val = " << Demo::getval() << endl;
    obj.getmyval();
    cout << "val = " << Demo::getval() << endl;
}
(3)const修饰对象

​ 表示对象是只读,即对象不能被修改

​ 即:

​ ①const对象只能调用const方法

​ ②const对象也可以调用static方法

​ ③const对象不能调用非const方法

class Demo
{
public:
    Demo(int v = 0) : myval(v) //初始化列表
    {
        myval = v;
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    void setmyval(int val)
    {
        this->myval = val;
    }

    static int getval()
    {
        return val;
    }

    int getmyval() const; //const修饰成员方法

private:
    int myval;
    static int val;
};

int Demo::val = 123; //static成员必须要在类外声明定义初始化

int Demo::getmyval() const
{
    val++; //static 不属于类对象,所以可以被const方法修改
    return this->myval;
}

int main()
{
    const Demo obj;//const 修饰对象
    
    //obj .setmyval(321);//error const对象不能调用非const方法,因为非const方法有可能会修改类对象

    
   	cout << "myval = " << obj.getmyval() << endl; //right const对象可以调用const方法,原因︰const方法不会修改类对象

    cout << "val = " << obj.getval() << endl;//right const对象可以调用static方法,原因:static方法不会修改类对象
}

九、友元(友员)

理解:

​ 类的朋友

​ 注:打破了类的封装和隐藏,导致程序的可维护性变差,慎用

1.友元函数

​ 普通函数是类的友元,类的权限对友元函数不限制,通过普通函数能访问类的私有成员

​ 友元函数的声明:

friend 类型 函数名(形式参数);

​ 友元函数的声明可以放在类的私有部分,也可以放在公有部分

eg:
	class Demo
    {
    public:
        Demo(int val = 0)  : myval(val)
        {
           cout << __func__ << ":" << __LINE__ << endl; 
        }
        Demo(const Demo & obj)
        {
            this->myval = obj.myval;
            cout << __func__ << ":" << __LINE__ << endl; 
        }
        ~Demo()
        {
           cout << __func__ << ":" << __LINE__ << endl; 
        }
        
        int getmyval() const
        {
            return this->myval;
        }
        
        
        friend void setmyval(Demo & obj,int val);//友元函数:setmyval是类的友元函数
        
        
    private:
        int myval;
    };

	void setmyval(Demo & obj,int val)
    {
        obj.myval = val;
    }
	
	int main()
    {
        Demo obj(123);
        
        setmyval(obj,888);
        
        cout << "myval = " << obj.getmyval() << endl;
        
        return 0;
    }

2.友元类

​ 一个类是另一个类的友元

eg:

class A;

class B
{
public:
    B(int val = 0)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    B(const B &obj)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~B()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    void setmyval(A &obj, int val);

private:
};


class A
{
public:
    A(int val = 0) : myval(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    A(const A &obj)
    {
        this->myval = obj.myval;
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~A()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    int getmyval() const
    {
        return this->myval;
    }

    friend class B; //友元类,即类B是类A的友元

private:
    int myval;
};

void B::setmyval(A &obj, int val)
{
    obj.myval = val;
}

int main()
{
    A a(123);
    B b;

    cout << "a.myval = " << a.getmyval() << endl;

    b.setmyval(a, 888);

    cout << "a.myval = " << a.getmyval() << endl;

    return 0;
}



3.友元成员函数

​ 一个类的成员函数是另外一个类的友元,可以通过友元成员函数访问另一个类的私有成员

eg:

class A;//前向声明

class B
{
public:
    B(int val = 0)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    B(const B &obj)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~B()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    void setmyval(A &obj, int val);

private:
};

class A
{
public:
    A(int val = 0) : myval(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    A(const A &obj)
    {
        this->myval = obj.myval;
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~A()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    int getmyval() const
    {
        return this->myval;
    }

    friend void B::setmyval(A &obj, int val); //友元成员函数,即类B的成员函数setmyval是类A的友元成员函数

private:
    int myval;
};

void B::setmyval(A &obj, int val)
{
    obj.myval = val;
}

int main()
    
{
    A a(123);
    B b;
    b.setmyval(a, 888);

    cout << "a.myval = " << a.getmyval() << endl;

    return 0;
}

总结:

​ (1)友元关系不能继承,即父类的友元不一定是子类的友元,要看在子类中有没有friend;

​ (2)友元关系不具有交换性,即类B是类A的友元,则类A不一定是类B的友元,要看在类B中有没有friend class A

​ (3)友元关系不具有传递性,即类A是类B的友元类,类B是类C的友元,,则类A不一定是类C的友元,仍需要看类C中有没有friend class A

十、运算符重载

1.简介

(1)目的:

​ 让自定义类型的变量像基本类型变量一样能够使用运算符进行运算+

(2)语法:

​ 返回值类型 operator 运算符(参数列表)

​ {

​ 函数体;

​ }

	eg:
	
	Demo operator +(Demo &obj1,Demo &obj2)

(3)能够被重载的运算符
运算符类型内容
算术运算符+ - * / % ++ –
关系运算符== != < > <= >=
逻辑运算符&& || !
位运算符& | ~ ^ << >>
赋值运算符= += -= *= /= %= &= |= ^= <<= >>=
内存new delete new [] delete []
其他()函数调用 ->成员访问 ->*成员指针访问 []
(4)不能被重载的运算符

​ . : 成员访问运算符

​ .* : 成员指针访问运算符

​ :: : 域运算符

​ sizeof : 长度运算符

​ ?: : 条件运算符

2.友元运算符重载:

(1)概念

​ 指运算符重载作为类的友元

​ 运算符重载函数的参数个数,应该与参与这个运算符的运算对象数量一样多

constT operator+(const T & l, const T & r);

(2)语法
	class类名
	{
	public:

​ 成员变量/成员函数
​ friend 返回值类型 operator运算符 (参数列表);//友元运算符重载

​ protected:
​ 成员变量/成员函数

​ private:
​ 成员变量/成员函数
​ };

eg:


class Demo
{
public:
    Demo(int val = 0) : val(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    int getval()
    {
        return this->val;
    }

    friend Demo operator+(Demo &obj1, Demo &obj2);
    friend Demo &operator++(Demo &obj);
    friend Demo operator++(Demo &obj, int val);
    friend Demo &operator+=(Demo &obj1, Demo &obj2);
    friend bool operator!(Demo &obj);
    friend ostream &operator<<(ostream &os, const Demo &obj);

private:
    int val;
};

//友元重载双目运算符' + '
Demo operator+(Demo &obj1, Demo &obj2)
{
    Demo tmp(obj1.val + obj2.val);
    return tmp;
}
//友员重载单目运算符前++    返回对象本身obj
Demo &operator++(Demo &obj)
{
    ++obj.val;
    return obj;
}
//友员重载单目运算符后++    返回新的对象
Demo operator++(Demo &obj, int val) //val用来区分前++和后++
{
    Demo tmp(obj.val++);
    return tmp;
}
//友元重载双目运算符+=
Demo &operator+=(Demo &obj1, Demo &obj2)
{
    obj1.val += obj2.val;
    return obj1;
}
//友元重载单目运算符!
bool operator!(Demo &obj)
{
    return (!obj.val);
}

//友元运算符重载输出运算符<<
ostream &operator<<(ostream &os, const Demo &obj)
{
    os << obj.val;
    return os;
}

int main()
{
    Demo A(0);
    Demo B(20);

    // Demo C = A + B;
    // // Demo C = operator+(A, B);
    // ++A;

    // Demo D = B++;

    // A += B;//等价于:operator+=(A,B);

    // cout << "A+B = " << C.getval() << endl;
    // cout << "A++ = " << A.getval() << endl;
    // cout << " D  = " << D.getval() << endl;
    // cout << "B++ = " << B.getval() << endl;
    // cout << " A = " << A.getval() << endl;
    // cout << !A << endl;
    cout << "B.val =" << B << endl;
}

3.成员运算符重载:

(1)概念

​ 重载函数如果是成员函数,则忽略第一个参数。第一个参数默认是this。

constT T::operator(const T & that);

(2)语法
	class类名
	{
	public:

​ 成员变量/成员函数
​ 返回值类型 operator运算符 (参数列表); //成员运算符重载

​ protected:
​ 成员变量/成员函数

​ private:
​ 成员变量/成员函数
​ };

eg:

class Demo
{
public:
    Demo(int val = 0, bool flag = true) : val(val), flag(flag)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    int getval() const
    {
        return this->val;
    }

    Demo &operator+=(Demo &obj);
    Demo operator<<(int bit);
    Demo &operator++();
    bool operator!();

private:
    int val;
    bool flag;
};

//成员运算符重载+=
Demo &Demo::operator+=(Demo &obj)
{
    this->val += obj.val;
    return *this;
}
//成员运算符重载<<
Demo Demo::operator<<(int bit)
{
#if 0
    Demo tmp(this->val << bit);
    return tmp;
#else
    return Demo(this->val << bit); //匿名对象
#endif
}
//成员运算符重载前++
Demo &Demo::operator++()
{
    ++(this->val);
    return *this;
}

//成员运算符重载!
bool Demo::operator!()
{
    return !(this->flag);
}

int main()
{
    Demo a(4, true);
    Demo b(20, false);

    Demo c(a << 2); //Demo c = a.operator(2);

    // ++a;

    // a += b; //a.operator+=(b);

    // cout << " a = " << a.getval() << endl;
    // cout << " c = " << c.getval() << endl;
    // cout << " ++a = " << a.getval() << endl;

    if (!a)
        cout << "a is false" << endl;
    else
        cout << "a is true" << endl;

    if (!b)
        cout << "b is false" << endl;
    else
        cout << "b is true" << endl;

    return 0;
}
注:

A、重载运算符限制在C++语言中已有的运算符范围内的允许重载的运算符之中,不能创建新的运算符
B、运算符重载的实质是函数重载,遵循函数重载的选择原则
C、重载之后的运算符不能改变运算符的优先级和结合性,也不能改变运算符操作数的个数及语法结构
D、运算符重载不能改变该运算符用于内部类型对象的含义
E、运算符重载是针对新类型数据的实际需要对原有运算符进行的适当的改造,重载的功能应当与原有功能相类似,避免没有目的地使用重载运算符
F、重载运算符的函数不能有默认的参数,否则就改变了运算符的参数个数
G、重载的运算符只能是用户自定义类型,否则就不是重载而是改变了现有的C++标准数据类型的运算符的规则
H、运算符重载可以通过成员函数的形式,也可是通过友元函数,非成员非友元的普通函数。

在这里插入图片描述

十一、模板

1.理解

​ 编写与类型无关的代码,即实现泛型编程

2.函数模板
(1)概念

​ 即用一个类型参数来代替函数的参数和返回值类型

​ 当函数功能、函数名一样但参数类型不一样时,可实现函数模板

(2)语法
	template <class	形参名,class	形参名.....>//类型参数列表
    //或template <typename	形参名,typename	形参名.....>
    返回值类型	函数名(形参列表)
    {
        函数体;
    }
	eg:
	template <typename T>
	T add(T a,T b)
	{
        return a+b;
    }
	int main()
    {
        cout << add(10,20) << endl;//根据实参类型自动匹配为:add(int, ints)
        cout << add<double>(1.3,4) << endl;//显示指定类型实参
            
        return 0;
    }
3.类模板
(1)概念

​ 当类中成员变量和成员方法的参数类型、返回值类型不一样,但成员属性和成员方法一样时,类可以定义为模板类

(2)语法
	template <typename 形参名,typename 形参名...>
	//或template <class 形参名,class 形参名...>
	class	类名
	{
        
    };
        
eg:
	#include <iostream>
using namespace std;
template <typename T>
class Demo
{
public:
    Demo(T val) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    T getval() const;
    void setval(T val);

private:
    T value;
};

template <typename T> //类外部自定义模板成员函数
T Demo<T>::getval() const
{
    return this->value;
}

template <typename T>
void Demo<T>::setval(T val)
{
    this->value = val;
}

int main()
{
    Demo<int> obj(10);
    obj.setval(22);

    cout << obj.getval() << endl;

    return 0;
}
4.非类型模板参数
(1)理解

​ 类型参数列表中定义了确定类型的变量,此变量叫非类型模板参数

(2)示例
eg:
	template <class T,int len>//T:类型模板参数 len:非类型模板参数
    class demo
    {};
(3)注意

注意:
1)非类型形参在模板定义的内部是常量值
2)非类型模板的形参只能是整型、指针和引用,像double,string,string **这样的类型是不允许的。但是double &, double *,对象的引用或指针是正确的。
3)非类型模板形参的实参如果是表达式,则必须是一个常量表达式,在编译时计算出结果。
4)非类型模板形参和实参间允许类型转换



eg:
template <typename T,int len>
class Array
{
public:
    Array()
    {
        ptr = new T [len];
        assert(ptr != nullptr);
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Array()
    {
        delete [] ptr;
        cout << __func__ << ":" << __LINE__ << endl;
    }
    
    
    void setval(int pos,T val);//设置数组元素值
  
    T getval(int pos);//获取下标对应的数组元素值
    
private:
    T *ptr;//数组首地址
};

template <typename T,int len>
void Array<T,len>::setval(int pos,T val)//设置数组元素值 
{
    ptr[pos] = val; 
}

template <typename T,int len>
T Array<T,len>::getval(int pos)//获取下标对应的数组元素值
{
    return ptr[pos];
}

int main()
{
    Array<int,10> a;
    
    for(int i = 0;i<10;i++)
        a.setval(i,i+1);
    
    for(int i = 0;i<10;i++)
        cout << a.getval(i) << " ";
    cout << endl;
    
    return 0;
}
5.默认类型模板参数默认值

理解:给模板参数指定默认值

eg:
	template <typename T = int,int len = 10>
6.友元模板函数

​ 指模板函数作为类的友元

eg:
	template <class T>
    class Demo
    {
    public:
        Demo(T value):val(value)
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }
        Demo(const Demo<T> &obj)
       	{
            this->val = obj.val;
            cout << __func__ << ":" << __LINE__ << endl;
        }
        ~Demo()
        {
            cout << __func__ << ":" << __LINE__ << endl;
        }
        
        template <class C>
        friend Demo<C> operator+(Demo<C> & obj1,Demo<C> & obj2);//友元模板运算符重载函数
        
        template <class A>
    	friend ostream &operator<<(ostream &os, const Demo<A> &obj);

        
    private:
        T val;
    };

	template <class C>
	Demo<C> operator+(Demo<C> & obj1,Demo<C> & obj2)
    {
        return Demo<C>(obj1.val +obj2.val);
    }

	template <class A>
    ostream &operator<<(ostream & os,const Demo<A> &obj)
    {
        os << obj.val;
        return os;
    }

	int main()
    {
     	Demo<int> a(123);
        Demo<int> b = a;
        
        cout << a+b <<endl;
        
        return 0;
    }

十二、类继承

1.目的

​ 实现代码的复用,提高编程效率

2.概念

​ 用一个已有的类创建新类的过程,叫类继承

​ 已有的类叫:基类/父类

​ 新类叫: 派生类/子类

3.派生类和基类

​ 派生类会继承基类所有的属性和方法,并且可以在基类的基础上新增子类自己特有的属性和方法,

同时若子类觉得基类的方法不符合使用要求,可以做出修改。

​ 关系:派生类是基类的具体化,基类是派生类的抽象

注:继承权限可省略,默认继承权限为private

4.语法
class 子类名: 继承权限 基类名1,继承权限 基类名2......
{
	...
};
5.继承权限
(1)public继承
基类的public权限            ->                子类的public权限
基类的protected权限         ->                子类的protected权限
基类的private权限           ->                子类不能直接访问,可通过基类的成员方法访问
//基类
class Base
{
public:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }

    int getvalue() const;

protected:
    void setvalue(int val);

private:
    int value;
};
void Base::setvalue(int val)
{
    this->value = val;
}
int Base::getvalue() const
{
    return this->value;
}

//派生类
class Inherit : public Base
{
public:
    Inherit(int val) : myval(val), Base(val) //在子类的初始化列表里给基类的构造函数传参
    {
        // setvalue(888);
        //value++; //error 基类的私有成员,只能通过接口访问
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }
    int getmyval() const
    {
        return myval;
    }

private:
    int myval;
};

int main()
{
    Inherit obj(123);
    //obj.setvalue(888);//setvalue在派生类中权限为protected不能直接访问,只能在类内部访问
    cout << "sizeof(obj) = " << sizeof(obj) << endl;

    cout << "基类:" << obj.getvalue() << endl;
    cout << "子类:" << obj.getmyval() << endl;

    return 0;
}
(2)protected继承
基类的public权限            ->                子类的protected权限
基类的protected权限         ->                子类的protected权限
基类的private权限           ->                子类不能直接访问,可通过基类的成员方法访问
//基类
class Base
{
public:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }

    
    int getvalue() const;
    void setvalue(int val);

private:
    int value;
};
void Base::setvalue(int val)
{
    this->value = val;
}
int Base::getvalue() const
{
    return this->value;
}

//派生类
class Inherit : protected Base
{
public:
    Inherit(int val) : myval(val), Base(val) //在子类的初始化列表里给基类的构造函数传参
    {
        
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }
    int getmyval() const
    {
        return myval;
    }

private:
    int myval;
};

int main()
{
	Inherit obj(123);
	cout << obj.getmyval() << endl;
	return 0;
}
(3)private继承
基类的public权限            ->                子类的private权限
基类的protected权限         ->                子类的private权限
基类的private权限           ->                子类不能直接访问,可通过基类的成员方法访问
//基类
class Base
{
public:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }

    
    int getvalue() const;
    void setvalue(int val);

private:
    int value;
};
void Base::setvalue(int val)
{
    this->value = val;
}
int Base::getvalue() const
{
    return this->value;
}

//派生类
class Inherit : private Base
{
public:
    Inherit(int val) : myval(val), Base(val) //在子类的初始化列表里给基类的构造函数传参
    {
        cout << getvalue() << endl;
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }
    int getmyval() const
    {
        return myval;
    }

private:
    int myval;
};

int main()
{
	Inherit obj(123);
	//cout << obj.getmyval() << endl;//error	私有成员方法只能在类内部访问
	return 0;
}
(4)protected继承和private继承的区别
//基类
class Base
{
public:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }

    
    int getvalue() const;
    void setvalue(int val);

private:
    int value;
};
void Base::setvalue(int val)
{
    this->value = val;
}
int Base::getvalue() const
{
    return this->value;
}

//派生类
class Inherit : private Base
{
public:
    Inherit(int val) : myval(val), Base(val) //在子类的初始化列表里给基类的构造函数传参
    {
        cout << getvalue() << endl;
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }
    int getmyval() const
    {
        return myval;
    }

private:
    int myval;
};

//Inherit 的派生类Subclass(私有继承):
//getvalue()/setvalue ()是基类的private成员,只能通过基类的接口访问
class Subclass : private Inherit
{
public:
    Subclass()
    {
   		cout << getmyval() <<endl;//访问Inherit的public接口,即自己的private接口
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Subclass()
    {
        cout << __func__ << ": " << __LINE__ << endl;
    }
    
private:
    
};

int main()
{
	Inherit obj(123);
	//cout << obj.getmyval() << endl;//error	私有成员方法只能在类内部访问
	return 0;
}
6.基类及派生类构造函数及析构函数的执行顺序
基类构造函数
派生类构造函数
...
派生类析构函数
基类析构函数
7.派生类继承基类的成员数据及方法

​ 基类的构造函数和析构函数不能被继承

​ 基类和派生类都有自己的构造函数和析构函数

十三、is_a关系和has_a关系

1.is-a

​ 用来描述继承关系;一个类是另外一个类的一种,此时两个类之间的关系为is-a关系,即is-a用来描述继承,满足is-a关系的两个类才能继承

2.has-a

​ 用来描述组合关系;当一个类中包含另外一个类的时候就用到has-a描述

十四、多重继承

1.理解

​ 当一个类有多个类的特性时需要从多个类派生

2.语法
class 派生类名:继承方式 基类名1,继承方式 基类名2......
{
  派生类类体  
};
eg:
class Wolf
{
publicWolf()
	{
    	cout << __func__ << ":" << __LINE__ << endl;
    }
	~Wolf()
	{
    	cout << __func__ << ":" << __LINE__ << endl;
    }
};
class Man
{
publicMan()
	{
    	cout << __func__ << ":" << __LINE__ << endl;
    }
	~Man()
	{
    	cout << __func__ << ":" << __LINE__ << endl;
    }
};
class Wolfman:public Wolf,public Man
{
publicWolfman()
	{
    	cout << __func__ << ":" << __LINE__ << endl;
    }
	~Wolfman()
	{
    	cout << __func__ << ":" << __LINE__ << endl;
    }
};

int main()
{
    Wolfman obj;
    return 0;
}

十五、多态

1.目的

​ 函数接口重用

2.概念

​ 同一接口(函数),面向不同对象,效果不同;

​ 多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。

3.静态多态(早绑定)

​ 编译时已经确定调用的版本

​ 如cout,函数重载,运算符重载,模板技术等

4.动态多态(晚绑定)

​ 运行时才确定是哪个版本

​ 通过虚函数实现

5.虚函数

​ 用virtual修饰的成员函数,就是虚函数。虚函数的作用就是实现多态性(Polymorphism)。多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。

虚函数的限制如下:

A、非类的成员函数不能定义为虚函数

B、类的静态成员函数不能定义为虚函数

C、构造函数不能定义为虚函数,但可以将析构函数定义为虚函数

D、只需要在声明函数的类体中使用关键字“virtual”将函数声明为虚函数,而定义函数时不需要使用关键字“virtual”。

E、当将基类中的某一成员函数声明为虚函数后,派生类中的同名函数(函数名相同、参数列表完全一致、返回值类型相关)自动成为虚函数。

6.语法
class	类名
{
public:
	virtual 返回值 函数名(参数列表);
protected:
	virtual 返回值 函数名(参数列表);
private:
	virtual 返回值 函数名(参数列表);
} ;

(1)静态链接:是指在编译阶段就将函数实现和函数调用关联起来
eg:
class Base
{
public:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    void setval(int val)
    {
        this->value = val;
    }
    int getval() const
    {
        return this->value;
    }
    void prnmsg()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

private:
    int value;
};

//子类
class Inherit : public Base
{
public:
    Inherit(int val = 0) : myval(val), Base(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    void prnmsg()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

private:
    int myval;
};
void test(Base &obj)//静态多态,传Base调用的就是Base类型的对象
{
    obj.prnmsg(); //prnmsg()是静态链接,即在编译时根据类型决定调用的函数,类型是Base,所以不管传递的参数是Base对象还是Inherit对象,都会调用Base的prnmsg()
}
int main()
{
    Base a;
    Inherit b;

    test(a);
    test(b); //隐式类型转换Inherit ->Base

    return 0;
}



(2)动态链接:是指在程序执行的时候才将函数实现和函数调用关联
class Base
{
public:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    void setval(int val)
    {
        this->value = val;
    }
    int getval() const
    {
        return this->value;
    }
    virtual void prnmsg()//虚函数
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

private:
    int value;
};

//子类
class Inherit : public Base
{
public:
    Inherit(int val = 0) : myval(val), Base(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    void prnmsg()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

private:
    int myval;
};
void test(Base &obj)
{
     obj.prnmsg(); //prnmsg()是动态链接即在运行时根据对象决定调用的函数,对象是Base就会调用Base对象的prnmsg(),对象是Inherit对象,就会调用Inherit的prnmsg()

}
int main()
{
    Base a;
    Inherit b;

    test(a);
    test(b); 

    return 0;
}

十六、重载、覆盖、隐藏

1.重载

​ 特点:

​ A、同一作用域

​ B、同名但参数不同(个数、类型、个数或类型都不同)

​ C、返回值无关

2.覆盖(重写)

​ (1)概念

​ 子类重写基类接口

​ (2)特点

​ A、不同的作用域(分别位于派生类与基类)

​ B、函数名字、参数、返回值相同

​ C、基类函数必须有virtual关键字,不能有static

​ D、重写函数的权限访问限定符可以不同

3.隐藏(重定义)

​ 特点:

​ A、不同作用域中:一个定义在基类中,一个定义在子类中(子类重写基类接口)

​ B、函数名、参数、返回值相同

​ C、基类、子类中不需要用virtual修饰

//1 2:重载
//3 4:重载
//1 3:覆盖(重写)
//2 4:隐藏
//1 4:隐藏
//2 3:隐藏

class Base
{
public:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    virtual void prnmsg() //虚函数            1
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    void prnmsg(int i) //                      2
    {
        cout << __func__ << ":" << __LINE__ << ": i = " << i << endl;
    }

private:
    int value;
};

//子类
class Inherit : public Base
{
public:
    Inherit(int val = 0) : myval(val), Base(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    void prnmsg() //                         3
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    void prnmsg(int i) //                    4
    {
        cout << __func__ << ":" << __LINE__ << ": i = " << i << endl;
    }

private:
    int myval;
};
void test(Base &obj)
{
    obj.prnmsg(); //prnmsg()是动态链接即在运行时根据对象决定调用的函数,对象是Base就会调用Base对象的prnmsg(),对象是Inherit对象,就会调用Inherit的prnmsg()
}
int main()
{
    Base a;
    Inherit b;

    test(a);
    test(b);

    a.prnmsg(123);
    b.prnmsg(456);

    return 0;
}

十七、抽象类

1.概念

​ 包含纯虚函数的类被称为抽象类

2.纯虚函数

​ 指虚函数只声明没有定义

eg: virtual void fun() = 0;

= 0 告诉编译器,函数没有主体,该虚函数为纯虚函数

3.特点

(1)含有纯虚函数的类就是抽象类。

(2)抽象类没有完整的信息,只能是派生类的基类

(3)抽象类不能有实例,不能有静态成员

(4)派生类应该实现抽象类的所有方法

4.语法
class	类名
{
public:
	virtual 返回值 函数名(参数列表) = 0;
protected:
	virtual 返回值 函数名(参数列表) = 0;
private:
	virtual 返回值 函数名(参数列表) = 0;
} ;
eg:
class Base
{
public:
    Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    virtual void prnmsg() = 0; //纯虚函数       1

private:
    int value;
};

//子类
class Inherit : public Base
{
public:
    Inherit()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    void prnmsg() //子类实现父类的纯虚接口
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

private:
    int myval;
};
int main()
{
    //Base a;//error 抽象类不能定义对象
    Inherit b;

    b.prnmsg();

    return 0;
}

十八、虚继承

​ 假设有类A为父类,类B和类C继承了类A,而类D即继承类B又继承类C(这种菱形继承关系)。当实例化D对象的时候,每个D的实例化对象中都有了两份完全相同的A的数据。因为保留多分数据成员的拷贝,不仅占用较多的存储空间,还增加了访问这些成员时的困难,而实际上并不需要多份的拷贝。

​ 针对这种情况,使用虚基类的方法,使得在继承间接共同基类只保留一份成员。

​ 继承时,虚继承先构造

即解决了多重继承产生的二义性

class Base
{
public:
    Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    void prnmsg()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
};
class Wolf : public virtual Base
{
public:
    Wolf()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Wolf()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
};
class Man : public virtual Base
{
public:
    Man()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Man()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
};
class Wolfman : public Man, public Wolf
{
public:
    Wolfman()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Wolfman()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    void prnmsg()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
};

int main()
{
    Wolfman obj;
    obj.prnmsg();

    return 0;
}

十九、虚析构

1.目的

​ 虚析构函数是为了解决:基类的指针指向派生类的对象,并用基类的指针释放派生类的对象出现的内存泄漏问题

2.注意

​ (1)基类为虚析构,子类析构自动为虚析构

​ (2)当析构函数为纯虚函数时,必须声明定义,空实现(哑元实现)即可;

:
class Demo
{
public:
    ~Demo() = 0;
};
Demo::~Demo(){}
eg:
class Base
{
public:
    Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    virtual ~Base() //基类为虚析构,子类析构自动为虚析构
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
};
class Inherit : public Base
{
public:
    Inherit()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit() //自动为虚析构
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
};
int main()
{
    Base *p = new Inherit;
    delete p;

    return 0;
}

二十、限制构造函数

1.概念

​ 构造函数的权限是protected/private

​ 通过对构造函数加以限制,防止通过类直接定义对象。只能通过特殊方法获取到对象。

eg1:将构造函数权限设置为protectedclass Base
{
protected:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    virtual ~Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

private:
    int value;
};

class Inherit : public Base
{
public:
    Inherit()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Inherit()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
};
int main()
{

    //Base a;//error 因为Base的构造函数是protected权限,所以不能构造对象
    Inherit b; //right

    return 0;
}
    
    
eg2:将构造函数权限设置为privateclass Base
{
private:
    Base(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

public:
    virtual ~Base()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    friend Base *getobj();//友元函数调用构造函数
    friend void freeobj(Base *p);

private:
    int value;
};


Base *getobj()
{
    Base *p = new Base;
    return p;
}
void freeobj(Base *p)
{
    delete p;
}
int main()
{
    Base *p = getobj();//调用友元实现构造
    freeobj(p);

    return 0;
}

二十一、异常

1.基本思想

​ 让库的设计者发现错误并抛出异常,库的使用者捕获异常并处理

2.异常处理机制由三部分组成
(1)try(检错)

​ try块中的代码标识将被激活的特定异常,它后面一般跟着一个或多个catch块

(2)throw(抛出异常)

​ 当问题出现时,程序会抛出一个异常,通过使用throw关键字完成

(3)catch(捕获异常)

​ 在你想要处理问题的地方,通过异常处理程序捕获异常catch关键字常用于捕获异常

3.语法

头文件

try
{
    检查错误
    if(错误)
        throw异常
}
catch(捕获异常1)
{
    处理异常
}
catch(捕获异常2)
{
    处理异常
}
......
4.c++标准异常

https://cplusplus.com/reference/exception/exception/

在这里插入图片描述

标准异常类

​ exeception(基类)

​ / \

​ logic_error runtime_error

标准异常基类(c++11):
    class exception {
public:
  exception () noexcept;
  exception (const exception&) noexcept;
  exception& operator= (const exception&) noexcept;
  virtual ~exception();
  virtual const char* what() const noexcept;
}

注:noexcept等价于throw()不抛出异常
    throw()在c++98版中出现


eg:

int division(int a, int b)
{
    if (b != 0)
        return a / b;
#if 0
    else
        return -1;
#endif

#if 0
    else
    {
       invalid_argument tmp("invalid argument b == 0"); //定义异常对象tmp
         throw tmp;
    }
#else
    else
        throw invalid_argument("invalid argument b == 0"); //【抛出异常
#endif
}

int main()
{
    int m = 1;
    int n = 1;
    try //检错
    {
        cout << division(m, n) << endl;
    }
    catch (const invalid_argument &error_obj) //捕获异常并判断异常类型
    {
        cout << error_obj.what() << endl;//调基类(exeception)中的what()获取异常原因
    }

    return 0;
}

5.自定义异常
(1).由标准异常派生
class Myexception : public exception //根据基类exception派生的自定义异常处理类
{
public:
    Myexception(const char *p) noexcept : ptr(p)
    {
    }
    ~Myexception() //自动为虚
    {
    }
    const char *what() const noexcept //自动为虚,重写基类的虚接口what()
    {
        return ptr;
    }

private:
    const char *ptr; //保存异常原因首地址
};
int division(int a, int b)
{
    if (a == 0)
        throw Myexception("invalid argument a == 0"); //调用根据基类exception派生的自定义异常处理函数
    else if (b == 0)
        throw invalid_argument("invalid argument b == 0"); //调用标准异常处理

    else
        return a / b;
}
int main()
{

    int m = 1;
    int n = 0;
    try //检错
    {
        cout << division(m, n) << endl;
    }
    catch (const Myexception &error_obj) //捕获异常并判断异常类型
    {
        cout << error_obj.what() << endl; //调基类(exeception)中的what()获取异常原因
    }
    catch (const invalid_argument &error_obj)
    {
        cout << error_obj.what() << endl;
    }
}
(2).完全自定义的异常

​ 不用继承基类exception

class Myexception 
{
public:
    Myexception(const char *p) noexcept : ptr(p)
    {
    }
    ~Myexception() 
    {
    }
    const char *what() const noexcept 
    {
        return ptr;
    }

private:
    const char *ptr; //保存异常原因首地址
};
int division(int a, int b)
{
    if (b == 0)
        throw Myexception("error b == 0"); //调用根据基类exception派生的自定义异常处理函数

    else
        return a / b;
}
int main()
{

    int m = 1;
    int n = 0;
    try //检错
    {
        cout << division(m, n) << endl;
    }
    catch (const Myexception &error_obj) //捕获异常并判断异常类型
    {
        cout << error_obj.what() << endl; //调基类(exeception)中的what()获取异常原因
    }
    
}

二十二、转换函数

1.功能

​ 实现基本类型和自定义类型之间的转换;

2.语法
operator 类型名()
{
    转换语句
}
3.基本规则

​ (1)转换函数只能是成员函数,无返回值,空参数。
​ (2)不能定义到void的转换,也不允许转换成数组或者函数类型。
​ (3)转换常定义为const形式,原因是它并不改变数据成员的值。

4.explicit关键字

​ 阻止隐式类型转换

eg:
class Demo
{
public:
    explicit Demo(int val ):value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    
    void setval(int val)
    {
        value = val;
    }
    int getval()
    {
        return value;
    }
private:
 	int value;
};

int main()
{
    Demo obj(0);
    
    //obj = 123; //Demo tmp(123); obj = tmp; int->Demo隐式类型转换
    obj = (Demo)123; //explicit不会阻止显示类型转换
    
    cout << obj.getval() << endl;
    
    return 0;
}
5.标准转换函数
(1)C++标准转换函数

编译时转换:reinterpret_cast、const_cast、static_cast
运行时候转换:dynamic_cast

(2)reinterpret_cast

语法:reinterpret_cast(expression)
将一个类型的指针转换为另一个类型的指针,它也允许从一个指针转换为整数类型

int main()
{
    char *p = NULL;
    int *q = NULL;
    
    //q = p; // error char* -> int *
    q = reinterpret_cast<int *> (p);//char* -> int *
    
    return 0;
}
(3)const_cast

语法:const_cast< new type>( expression)
const指针与普通指针间的相互转换,注意:不能将非常量指针变量转换为普通变量

int main()
{
    const int *q =  NULL;
    int *p = NULL;
    
    //p = q;//error const int* -> int *
    p = const_cast<int *> (q);
    
    return 0;
}
(4)static_cast

语法:static_cast(expression)
主要用于基本类型间的相互转换,和具有继承关系间的类型转换

class Base
{
public:
    Base()
    {
    };
    ~Base()
    {
    };
private:
    
};
class Inherit:public Base
{
public:
    Inherit()
    {
    };
    ~Inherit()
    {
    };
private:
    
};

int main()
{
    Base *p = nullptr;
    Inherit *q = nullptr;
    
    //q = p;//error Base * -> Inherit *
    q = static_cast<Inherit *> (p);
    
    return 0;
}
(5)dynamic_cast

语法:dynamic_cast(expression)
只有类中含有虚函数才能用dynamic_cast;仅能在继承类对象间转换
dynamic_cast具有类型检查的功能,比static_cast更安全

class Base
{
public:
    Base(){};
    virtual ~Base(){};   
};
class Inherit:public Base
{
public:
    Inherit(){};
    ~Inherit(){};   
};

int main()
{
    Base *p = nullptr;
    Inherit *q = nullptr;
    
    //q = p;//error Base * -> Inherit *
    q = dynamic_cast<Inherit *> (p);
    
    return 0;
}

6.自定义转换函数

语法:

operator 类型名()
{
    转换语句
}
class Demo
{
public:
    explicit Demo(int val):value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    virtual ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    
    operator int()
    {
        return this->value;
        cout << __func__ << ":" << __LINE__ << endl;
    }
private:
    int value;
};
int main()
{
    Demo obj(123);
    
    int i;
    i = obj;// Demo -> int
    // i = obj.operator int();//等价于调用operator int函数
    
    cout << "i = " << i << endl;
    
    return 0;
}

二十三、智能指针

1.解决

​ 实现了c++的自动内存回收机制(即解决只关系malloc不关心free的问题)

2.本质

​ 类模板

​ 重载指针运算符* ->

​ 智能指针通用实现技术是使用引用计数(reference count)。智能指针类将一个计数器与类指向的对象相关联,引用计数跟踪该类有多少个对象的指针指向同一对象。

​ 头文件

3.shared-ptr(共享智能指针)

​ 共享指针,即多个指针共享同一个空间

eg:
class Demo
{
public:
    explicit Demo(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    virtual ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    int getval()
    {
        return value;
    }
    void setval(int val)
    {
        value = val;
    }

private:
    int value;
};

int main()
{
    shared_ptr<Demo> p(new Demo); //p指针指向对象Demo
    shared_ptr<Demo> q(p);

    p->setval(666); //等价于(*p)setval(666);
    cout << p->getval() << endl;

    q->setval(666);
    cout << q->getval() << endl;
    
    return 0;
}
shared_ptr类:
    template <class T>
    class Myshared_ptr
    {
    public:
        Myshared_ptr(T *p):ptr(p)
        {}
        Myshared_ptr(const Myshared_ptr<T> &obj)
        {
            ptr = obj.ptr;
        }
        ~Myshared_ptr()
        {
            计数器--;
            if(计数器值 == 0)
                delete ptr;
        }
        operator ->()
        {}
        operator *()
        {}
    private:
       T *ptr; 
    };
4.unique_ptr(唯一/独享智能指针)

​ 只能有一个指针指向对象,即同一时刻只能有一个unique_ptr指向给定对象(通过禁止拷贝语义、只有移动语义来实现)。

class Demo
{
public:
    explicit Demo(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    virtual ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    int getval()
    {
        return value;
    }
    void setval(int val)
    {
        value = val;
    }

private:
    int value;
};

int main()
{
    unique_ptr<Demo> p(new Demo); //p指针指向对象Demo
   
    p->setval(666); //等价于(*p)setval(666);
    cout << p->getval() << endl;

    return 0;
}
5.weak-ptr(弱指针)

​ 配合共享指针,判断对象是否存在

class Demo
{
public:
    explicit Demo(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    virtual ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    int getval()
    {
        return value;
    }
    void setval(int val)
    {
        value = val;
    }

private:
    int value;
};

int main()
{
    shared_ptr<Demo> p(new Demo); //p指针指向对象Demo
    weak_ptr<Demo> q(p);
    
    p.reset();//释放指针指向的对象

   if(q.expired())//判断q指向的对象是否存在
       cout << "obj is exist" << endl;
    
    
    return 0;
}

二十四、STL(Standard Template Library)

1.概念

​ STL主要是一些”容器”的集合

2.STL主要组成部分
(1)容器(containers)

​ 特殊的数据结构,实现了数组、链表、队列、等等,实质是模板类

(2)迭代器(iterators)

​ 用来操作容器的特殊指针,一种复杂的指针,可以通过其读写容器中的对象,实质是运算符重载

(3)空间配置器(allocator)

​ 给容器分配空间,容器的空间配置管理的模板类

(4)配接器(adapters)

​ 用来修饰容器、仿函数、迭代器接口

(5)算法(algorithms)

​ 读写容器对象的逻辑算法:排序、遍历、查找、等等,实质是模板函数

(6)仿函数(functors)

​ 类似函数,通过重载()运算符来模拟函数行为的类

(7)关系

​ container(容器) 通过 allocator(配置器) 取得数据储存空间,algorithm(算法)通过 iterator(迭代器)存取container(容器) 内容,functor(仿函数) 可以协助algorithm(算法) 完成不同的策略变化,adapter(配接器) 可以修饰或套接functor(仿函数)。

3.容器

(1)序列容器

​ 容器中元素按照线性结构组织起来,可以逐个读写元素。主要代表:vector(数组/向量)、dequeue(双端队列)、list(链表)

(2)关联容器

​ 关联容器通过键(key)存储和读取元素,主要有:map(映射/图)、set(集合)等

(3)容器适配器

​ 是对前面提到的某些容器(如vector)进行再包装,使其变为另一个容器。典型的有:栈(stack)、队列(queue)等。

4.STL序列容器
1.vector(数组)
(1)头文件

(2)vector向量相当于一个数组

​ 在内存中分配一块连续的内存空间进行存储。支持不指定vector大小的存储。通常此默认的内存分配能完成大部分情况下的存储。

(3)优点:

​ 可以不指定大小,使用push_back、pop_back来进行动态操作

​ 随机访问方便,即支持[ ]操作符和vector.at()

​ 节省空间

(4)缺点:

​ 在内部进行插入删除操作效率低

​ 只能在vector的最后进行push和pop,不能在vector的头进行push和pop

​ 当动态添加的数据超过vector默认分配的大小时要进行整体的重新分配、拷贝与释放

eg:

int main()
{
    int i = 0;
    vector<int> a(10, 0); //定义有10个int型元素的数组,值全部初始化为0

    vector<int>::iterator p = a.begin(); //让p指向数组首元素

    //遍历数组元素
    for (i = 0; i < 10; i++)
        cout << a[i] << " "; //等价:a.at(i)
    cout << endl;

    //尾插
    for (i = 0; i < 10; i++)
        a.push_back(i * 2);

    //遍历数组元素
    for (i = 0; i < a.size(); i++)
        cout << a.at(i) << " ";
    cout << endl;

    //出栈(从尾部出)
    for (i = 10; i > 0; i--)
        a.pop_back();

    //用iterator遍历数组元素
    p = a.begin();
    for (i = 0; i < a.size(); i++)
        cout << *(p++) << " ";
    cout << endl;

    //擦除
    // vector<int>::iterator start = a.begin();
    // vector<int>::iterator end = a.end();
    // a.erase(start, end);

    a.erase(a.begin(), a.end());
    cout << a.size() << endl;

    return 0;
}
2.list(双向链表)
(1)概念

​ 每一个结点都包括一个信息快Info、一个前驱指针Pre、一个后驱指针Post。可以不分配必须的内存大小方便的进行添加和删除操作。使用的是非连续的内存空间进行存储。

(2)优点

​ 不使用连续内存完成动态操作

​ 在内部方便的进行插入和删除操作

​ 可在两端进行push、pop

(3)缺点

​ 不能进行内部的随机访问,即不支持[ ]操作符和vector.at()

​ 相对于verctor占用内存多

int main()
{
    int i = 0;
    list<int> a;

    list<int>::iterator p = a.begin();

    for (i = 0; i < 10; i++)
        // a.push_back(i + 1);
        a.insert(p, i + 1);

    p = a.begin();
    for (i = 0; i < a.size(); i++)
        cout << *(p++) << " ";
    cout << endl;

    p = a.begin();
    for (int m = 0; m < 5; m++)
        p++;
    list<int>::iterator q;
    q = a.begin();

    a.erase(q, p);

    p = a.begin();
    for (i = 0; i < a.size(); i++)
        cout << *(p++) << " ";
    cout << endl;
    return 0;
}

getval() << endl;

return 0;

}






#### 5.weak-ptr(弱指针)

​	配合共享指针,判断对象是否存在

```cpp
class Demo
{
public:
    explicit Demo(int val = 0) : value(val)
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }
    virtual ~Demo()
    {
        cout << __func__ << ":" << __LINE__ << endl;
    }

    int getval()
    {
        return value;
    }
    void setval(int val)
    {
        value = val;
    }

private:
    int value;
};

int main()
{
    shared_ptr<Demo> p(new Demo); //p指针指向对象Demo
    weak_ptr<Demo> q(p);
    
    p.reset();//释放指针指向的对象

   if(q.expired())//判断q指向的对象是否存在
       cout << "obj is exist" << endl;
    
    
    return 0;
}

二十四、STL(Standard Template Library)

1.概念

​ STL主要是一些”容器”的集合

2.STL主要组成部分
(1)容器(containers)

​ 特殊的数据结构,实现了数组、链表、队列、等等,实质是模板类

(2)迭代器(iterators)

​ 用来操作容器的特殊指针,一种复杂的指针,可以通过其读写容器中的对象,实质是运算符重载

(3)空间配置器(allocator)

​ 给容器分配空间,容器的空间配置管理的模板类

(4)配接器(adapters)

​ 用来修饰容器、仿函数、迭代器接口

(5)算法(algorithms)

​ 读写容器对象的逻辑算法:排序、遍历、查找、等等,实质是模板函数

(6)仿函数(functors)

​ 类似函数,通过重载()运算符来模拟函数行为的类

(7)关系

​ container(容器) 通过 allocator(配置器) 取得数据储存空间,algorithm(算法)通过 iterator(迭代器)存取container(容器) 内容,functor(仿函数) 可以协助algorithm(算法) 完成不同的策略变化,adapter(配接器) 可以修饰或套接functor(仿函数)。

3.容器

(1)序列容器

​ 容器中元素按照线性结构组织起来,可以逐个读写元素。主要代表:vector(数组/向量)、dequeue(双端队列)、list(链表)

(2)关联容器

​ 关联容器通过键(key)存储和读取元素,主要有:map(映射/图)、set(集合)等

(3)容器适配器

​ 是对前面提到的某些容器(如vector)进行再包装,使其变为另一个容器。典型的有:栈(stack)、队列(queue)等。

4.STL序列容器
1.vector(数组)
(1)头文件

(2)vector向量相当于一个数组

​ 在内存中分配一块连续的内存空间进行存储。支持不指定vector大小的存储。通常此默认的内存分配能完成大部分情况下的存储。

(3)优点:

​ 可以不指定大小,使用push_back、pop_back来进行动态操作

​ 随机访问方便,即支持[ ]操作符和vector.at()

​ 节省空间

(4)缺点:

​ 在内部进行插入删除操作效率低

​ 只能在vector的最后进行push和pop,不能在vector的头进行push和pop

​ 当动态添加的数据超过vector默认分配的大小时要进行整体的重新分配、拷贝与释放

eg:

int main()
{
    int i = 0;
    vector<int> a(10, 0); //定义有10个int型元素的数组,值全部初始化为0

    vector<int>::iterator p = a.begin(); //让p指向数组首元素

    //遍历数组元素
    for (i = 0; i < 10; i++)
        cout << a[i] << " "; //等价:a.at(i)
    cout << endl;

    //尾插
    for (i = 0; i < 10; i++)
        a.push_back(i * 2);

    //遍历数组元素
    for (i = 0; i < a.size(); i++)
        cout << a.at(i) << " ";
    cout << endl;

    //出栈(从尾部出)
    for (i = 10; i > 0; i--)
        a.pop_back();

    //用iterator遍历数组元素
    p = a.begin();
    for (i = 0; i < a.size(); i++)
        cout << *(p++) << " ";
    cout << endl;

    //擦除
    // vector<int>::iterator start = a.begin();
    // vector<int>::iterator end = a.end();
    // a.erase(start, end);

    a.erase(a.begin(), a.end());
    cout << a.size() << endl;

    return 0;
}
2.list(双向链表)
(1)概念

​ 每一个结点都包括一个信息快Info、一个前驱指针Pre、一个后驱指针Post。可以不分配必须的内存大小方便的进行添加和删除操作。使用的是非连续的内存空间进行存储。

(2)优点

​ 不使用连续内存完成动态操作

​ 在内部方便的进行插入和删除操作

​ 可在两端进行push、pop

(3)缺点

​ 不能进行内部的随机访问,即不支持[ ]操作符和vector.at()

​ 相对于verctor占用内存多

int main()
{
    int i = 0;
    list<int> a;

    list<int>::iterator p = a.begin();

    for (i = 0; i < 10; i++)
        // a.push_back(i + 1);
        a.insert(p, i + 1);

    p = a.begin();
    for (i = 0; i < a.size(); i++)
        cout << *(p++) << " ";
    cout << endl;

    p = a.begin();
    for (int m = 0; m < 5; m++)
        p++;
    list<int>::iterator q;
    q = a.begin();

    a.erase(q, p);

    p = a.begin();
    for (i = 0; i < a.size(); i++)
        cout << *(p++) << " ";
    cout << endl;
    return 0;
}
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值