C++基础知识总结

2023/6/18

本篇章记录学习过程C++的基础概念和代码测试实现,还有很多需要补充。一是还不清楚,二是还没有学到。打算学习过程中后面再做补充。先看完《C++primer 》书之后再慢慢来添加补充

1.函数重载

  1. 一个函数名可以实现多个功能,这取决于函数参数不同来实现判断对应的功能,与返回值无关
  2. 函数可以重载,构造函数,成员函数都可以重载,但是,析构函数不能重载
#include <iostream>

using namespace std;

void print()
{
    cout << "没有参数的print函数" << endl;
}

//int print() 错误
//{
//    cout << "没有参数的print函数" << endl;
//}
void print(int a)
{
    cout << "一个int参数的print函数"<< a << endl;
}
void print(string s)
{
    cout << "一个string参数的print函数" << s << endl;
}
void print(int a,int b)
{
    cout << "两个int参数的print函数" << a << b << endl;
}

int main()
{
    // 通过传入参数的不同,可以调用不同的重载函数
    print(1);
    print(1,2);
    print();
    print("hjahhah");

    return 0;
}

2.函数默认参数

函数可以设定默认值,当调用函数时,可以不传递参数,这样就会使用默认值。
注意点:

  1. 函数声明与定义分离,函数的参数默认值可以写在声明或定义处,但是只能出现一次
  2. 遵循向右原则,这个原则就是说某个参数设定了默认值,那么它的右边参数必须设定默认值
  3. 默认参数与函数重载一起使用时,需要注意不能发生二义性
void add(int a =1,int b=2,int c=3);

add(5);//这样是a为5,其余是默认b=2,c=3
add(7,8);//这样a=7,b=8,默认值c=3;
向右原则就是这样,a如果是默认值,那么b和c一定是默认值,不能b为默认值,c为传参。
#include <iostream>

using namespace std;

void show(int a = 1,int b = 2,int c = 3)
{
    cout << a << " " << b << " " << c << endl;
}

void print(int a,int b);

void print(int a=1,int b =1)
{
    cout << a << " " << b << endl;
}

void print2(int a = 1);

void print2(int a)
{
    cout << a << endl;
}


int main()
{
    show(); // 1 2 3
    show(6); // 6 2 3
    show(5,6); // 5 6 3
    show(6,7,8); // 6 7 8

    print(); // 1 1
    print(2); // 2 1
    print(2,3); // 2 3

    print2(); // 1
    print2(2); // 2

    return 0;
}
#include <iostream>

using namespace std;

void show(int a = 1,int b = 2,int c = 3)
{
    cout << a << " " << b << " " << c << endl;
}

void show()
{
    cout << "哈哈哈哈哈" << endl;
}


int main()
{
//    show(); 错误:二义性

    return 0;
}

3.引用

&可以改变引用的变量的值
注意点:

  1. 可以改变引用值,但是不能再次成为其他变量的引用
  2. 声明引用时,需要初始化
  3. 初始化的值不能为NULL
  4. 初始值是纯数字,需要加const关键字来修饰引用,表示引用的值不可变
  5. 可以将变量引用的地址赋值给一个指针,此处指针指向的还是原来的变量
  6. 可以对指针建立引用
  7. 使用const关键词修饰引用,此时不能通过引用修改数值,但是可以修改引用原变量的数值
#include <iostream>

using namespace std;


int main()
{
    int a = 1;
    int b = 2;
    int& c = a; // c是a的引用
    c = b; // 把b的值赋给c
    cout << a << " " << &a << endl; // 2 0x61fe88
    cout << b << " " << &b << endl; // 2 0x61fe84
    cout << c << " " << &c << endl; // 2 0x61fe88
//    int& c = b; 错误 变量c已经引用了a,现在再引用b
//    &c = b; 错误 

    return 0;
}
#include <iostream>
using namespace std;

int main()
{
    int a = 1;
//    int& b; 错误
    b = a;

    return 0;
}
#include <iostream>
using namespace std;
int main()
{
//    int& a = NULL; 错误

    return 0;
}
#include <iostream>

using namespace std;


int main()
{
    // 常引用
    const int &a = 123;
//    a++; 错误
    cout << a << endl; // 123
    return 0;
}
#include <iostream>

using namespace std;


int main()
{
    int a = 1;
    int &b = a;
    int* c = &b; // 指针c指向b
    a++;
    cout << *c << endl; // 2

    return 0;
}
#include <iostream>
using namespace std;
int main()
{
    int a = 1;
    int* b = &a; // b是a的指针
    int*& c = b; // c是b的引用
    cout << &a << " " << a << endl; // 0x61fe88 1
    cout << b << " " << *b << endl; // 0x61fe88 1
    cout << c << " " << *c << endl; // 0x61fe88 1

    return 0;
}
#include <iostream>
using namespace std;
int main()
{
    int a = 1;
    const int &b = a; // b是a的常引用
//    b++; 错误
    a++;
    cout << b << endl; // 2

    return 0;
}

4.对象

对象是面向对象编程思想的核心,面向对象的三个基本特征:封装、继承、多态
这三个就是核心了,要讲清楚需要的篇章太多了。

4.1创建对象

创建对象的前提是有一个类,然后就选择是栈内存对象 还是 堆内存对象

  • 栈内存对象生命周期在所在的{}结束后,自动销毁
  • 堆内存对象在delete关键字销毁,不手动销毁,对象持续存在
class Witch//创建了类
{
}

int main()
{
    Witch con1;//con1为对象,栈内存对象
    Witch* con2=new Witch//con2对象 ,堆内存对象
}

4.2封装的体现

class Witch//创建了类
{
	private: // 私有权限:只能在类内部访问
	public: //公有权限,全局,派生类,类内都可以访问。派生类就是继承的类
	protected: //保护权限,全局无法访问
}

|
| 类内访问 | 派生类内访问 | 全局访问 |
| --- | --- | --- | --- |
| 私有权限 private | √ | X | X |
| 保护权限 protected | √ | √ | X |
| 公有权限 public | √ | √ | √ |

使用prvite将类中成员隐藏,开放public接口公开访问

#include <iostream>

using namespace std;

/**
 * @brief 所有类名都要使用帕斯卡(大驼峰)命名法,即所有单词的首字母使用大写
 */
class MobilePhone
{
private: // 私有权限:只能在类内部访问
    string brand; // 可读可写
    string model; // 只写
    int weight = 188; // 只读,赋予了初始值

public:
    string get_brand() // 读函数:getter
    {
        return brand;
    }

    void set_brand(string b) // 写函数:setter
    {
        brand = b;
    }

    void set_model(string m) // setter
    {
        model = m;
    }

    int get_weight() // getter
    {
        return weight;
    }
};

int main()
{
    MobilePhone mp1;
    mp1.set_brand("华为");
    mp1.set_model("P60");
    cout << mp1.get_brand() << endl;
    cout << mp1.get_weight() << endl;

    MobilePhone* mp2 = new MobilePhone;
    mp2->set_brand("魅族");
    mp2->set_model("20");
    cout << mp2->get_brand() << endl;
    cout << mp2->get_weight() << endl;
    delete mp2;

    return 0;
}

4.2.1构造函数

创建类对象,代码就会进入构造函数,没写,默认一个构造函数
构造函数有以下特点:

  1. 函数名称与类名完全相同
  2. 构造函数不写返回值
  3. 构造函数支持函数重载
  4. 构造函数可以使用初始化列表赋值
class Witch//创建了类
{
	Witch()//构造函数
	{   }
	Witch(int a)//构造函数函数重载
	{   }
	Witch(int a,string b):num(a),modl(b)//初始化列表,num=a,modl=b;
	{   }
	

	private: // 私有权限:只能在类内部访问
        	int num=0;
        	string modl;
	public: //公有权限,全局,派生类,类内都可以访问。派生类就是继承的类
	protected: //保护权限,全局无法访问
}

4.2.2拷贝构造函数

每个类提供一个重载的拷贝构造函数,用于对象的拷贝,即基于某个对象创建一个数据完全相同的对象。注意点

  1. 新创建的对象与原来对象是两个对象
  2. 浅拷贝(当类中出现了指针类型的成员变量时,默认的拷贝构造函数会造成浅拷贝的问题。不同对象的成员变量会指向同一个区域,不符合面向对象的设计。)
  3. 深拷贝(解决浅拷贝)
  4. 隐式调用构造函数(使用explicit修饰构造函数后,就只支持显示调用了)
class Witch//创建了类
{
	Witch()//构造函数
	{   }
	Witch(const Witch& w)//默认的拷贝构造函数
	{   }
}
#include <iostream>
#include <string.h>

using namespace std;

class Dog
{
private:
    char* name;

public:
    Dog(char* n)
    {
        name = n;
    }
    // 复原浅拷贝(写不写都行)
    Dog(const Dog& d)
    {
        name = d.name;
    }

    void show()
    {
        cout << name << endl;
    }
};


int main()
{
    char c[20] = "wangcai";

    Dog d1(c);//构造函数
    // 拷贝构造函数
    Dog d2(d1);

    strcpy(c,"xiaobai");

    d1.show(); // xiaobai
    d2.show(); // xiaobai
    //出现了指向同一位置导致数据发生改变


    return 0;
}
#include <iostream>
#include <string.h>

using namespace std;

class Dog
{
private:
    char* name;

public:
    Dog(char* n)
    {
        // 单独开辟一块堆内存
        name = new 
            
        strcpy(name,n);
    }

    // 深拷贝
    Dog(const Dog& d)
    {
        // 单独开辟一块堆内存
        name = new char[20];
        strcpy(name,d.name);
    }

    void show()
    {
        cout << name << endl;
    }
};


int main()
{
    char c[20] = "wangcai";

    Dog d1(c);
    // 拷贝构造函数
    Dog d2(d1);

    strcpy(c,"xiaobai");

    d1.show(); // wangcai
    d2.show(); // wangcai


    return 0;
}
#include <iostream>

using namespace std;

class Teacher
{
private:
    string name;

public:
    // 使用explicit修饰构造函数后,只支持显式调用
    Teacher(string n)
    {
        cout << "创建了一个老师" << endl;
        name = n;
    }

    string get_name()
    {
        return name;
    }
};

int main()
{
        string name  = "罗翔";
    // 隐式调用构造函数
    Teacher t = name; // 创建了一个老师对象
    cout << t.get_name() << endl; // 罗翔

    return 0;
}

4.2.3析构函数

析构函数是类中与构造函数完全对立的函数。

构造函数 析构函数
手动调用后创建对象 对象销毁时自动调用
可以有参数,支持重载和参数默认值 无参数
函数名称是类名 函数名称是~类名
通常用于对象创建时初始化 通常用于对象销毁时回收内存和资源
#include <iostream>
#include <string.h>

using namespace std;

class Dog
{
private:
    char* name;

public:
    Dog(char* n)
    {
        // 单独开辟一块堆内存
        name = new char[20];
        strcpy(name,n);
    }
    // 复原浅拷贝(写不写都行)
    Dog(const Dog& d)
    {
        // 单独开辟一块堆内存
        name = new char[20];
        strcpy(name,d.name);
    }
    void show()
    {
        cout << name << endl;
    }
    // 析构函数
    ~Dog()
    {
        delete name; //这里加上,就解决了深拷贝开辟空间的释放问题
    }
};
int main()
{
    char c[20] = "wangcai";

    Dog d1(c);
    // 拷贝构造函数
    Dog d2(d1);

    strcpy(c,"xiaobai");

    d1.show(); // wangcai
    d2.show(); // wangcai

    return 0;
}

4.2.4.类内声明,类外定义

#include <iostream>

using namespace std;

class Student
{
private:
    string name = "张三";
public:
    // 声明
    void study();
};
// 定义
void Student::study()
{
    cout << name << "在努力学习!" << endl;
}
int main()
{
    Student s;
    s.study(); // 张三在努力学习!

    return 0;
}

4.2.5.this指针

是一种特殊的指针,只能在类的成员函数,构造函数,析构函数中使用。this指针指向当前类的对象首地址
作用:

  1. 区分局部变量和成员变量
  2. 链式调用
#include <iostream>

using namespace std;

class Test
{
public:
    Test()
    {
        cout << this << endl;
    }
};

int main()
{
    Test t1; // 0x61fe7b
    cout << &t1 << endl; // 0x61fe7b
    Test* t2 = new Test; // 0x8f0fe0
    cout << t2 << endl; // 0x8f0fe0
    delete t2;

    return 0;
}
#include <iostream>

using namespace std;

class Computer
{
private:
    string brand;

public:
    Computer(string brand)
    {
        // 区分重名
        this->brand = brand;
    }

    string get_brand()
    {
        // 所有的成员在类内部调用都是靠this指针,平常可省略
        return this->brand;
    }

    void show()
    {
        // 所有的成员在类内部调用都是靠this指针,平常可省略
        cout << this->get_brand() << endl;
    }
};

int main()
{
    Computer c("联想");
    cout << c.get_brand() << endl; // 联想
    c.show(); // 联想


    
#include <iostream>

using namespace std;

class Number
{
private:
    int value;

public:
    Number(int value):value(value) // 重名时使用构造初始化列表也可以
    {}

    Number& add(int v) // 支持链式调用
    {
        value += v;
        return *this; // 固定用法
    }

    int get_value()
    {
        return 
             ;
    }
};


int main()
{
    Number n1(1);
    // 传统方式:非链式调用
    n1.add(2);
    n1.add(3);
    n1.add(4);
    cout << n1.get_value() << endl; // 10

    Number n2(1);
    // 链式调用
    cout << n2.add(2).add(3).add(4).get_value() << endl; // 10

    // string类的append函数在源代码中也采用了链式调用的设计
    string s = "A";
    cout << s.append("B").append("C").append("D") << endl; // ABCD

    return 0;
}

4.3继承的体现

继承是在一个已经存在的类的继承上建立一个新的类,新的类拥有已经存在的类的特性,已经存在的类被称为“基类”或“父类”;新建立的类被称为“派生类”或“子类”。

class Father
{
}
class Son:public Father
{}

4.3.1析构与构造不能被继承

在继承中,任何一个派生类的任意一个构造函数都必须之间或间接调用基类的任意一个构造函数。因为在创建派生类对象时,需要调用基类的代码,使用基类的逻辑去开辟部分继承的空间

#include <iostream>

using namespace std;

class Father
{
private:
    string first_name;

public:
    Father(string first_name)
    {
        this->first_name = first_name;
    }

    string get_first_name() const
    {
        return first_name;
    }
};

class Son:public Father
{

};


int main()
{
//    Son s1("张"); 错误:构造函数不能被继承
//    Son s1; 错误:找不到Father::Father()构造函数

    return 0;
}

4.3.2派生类构造函数调用基类构造函数

  • 透传构造
  • 委托构造
  • 继承构造

4.3.2.1透传构造

透传构造是派生类的构造函数中直接调用基类的构造函数

#include <iostream>

using namespace std;

class Father
{
private:
    string first_name;

public:
    Father(string first_name)
    {
        this->first_name = first_name;
    }

    string get_first_name() const
    {
        return first_name;
    }
};

class Son:public Father
{
public:
//    Son():Father(){} 如果不写构造函数,编译器自动添加
    Son():Father("王"){} // 透传构造
    Son(string name)
        :Father(name){} // 透传构造
};


int main()
{
    Son s1;
    cout << s1.get_first_name() << endl;
    Son s2("张");
    cout << s2.get_first_name() << endl;

    return 0;
}

4.3.2.2委托构造

同一个类中的构造函数可以调用另外一个重载构造函数,注意点是,最终必须有一个构造函数透传调用基类的构造函数

#include <iostream>

using namespace std;

class Father
{
private:
    string first_name;

public:
    Father(string first_name)
    {
        this->first_name = first_name;
    }

    string get_first_name() const
    {
        return first_name;
    }
};

class Son:public Father
{
public:
    Son():Son("王"){} // 委托构造
    Son(string name)
        :Father(name){} // 透传构造
};

int main()
{
    Son s1;
    cout << s1.get_first_name() << endl;
    Son s2("张");
    cout << s2.get_first_name() << endl;

    return 0;
}

4.3.2.3继承构造

继承构造是C++11的特性,并不是真正的继承构造函数,而是编译器自动为派生类创建n个构造函数。

#include <iostream>

using namespace std;

class Father
{
private:
    string first_name;

public:
    Father(string first_name)
    {
        this->first_name = first_name;
    }

    Father():Father("张"){}

    string get_first_name() const
    {
        return first_name;
    }
};

class Son:public Father
{
public:
    // 一句话搞定
    using Father::Father;

    // 相当于添加了下面的代码
//    Son(string first_name):
//        Father(first_name){}

//    Son():Father(){}
};


int main()
{
    Son s1;
    cout << s1.get_first_name() << endl;
    Son s2("张");
    cout << s2.get_first_name() << endl;

    return 0;
}

4.3.3.对象的创建和销毁流程

  1. 创建与销毁流程完全对称。
    2. 创建过程中,同类型功能都是基类先执行,派生类后执行。
    3. 静态成员变量的生命周期与程序运行的周期相同。
    通过上述例子,可以看到面向对象编程的特点:编写效率高,执行效率低。
#include <iostream>

using namespace std;

/**
 * @brief The Value class 作为其他类的变量
 */
class Value
{
private:
    string name;

public:
    Value(string name):name(name)
    {
        cout << name << "创建了" << endl;
    }

    ~Value()
    {
        cout << name << "销毁了" << endl;
    }
};

class Father
{
public:
    Value value = Value("Father类的成员变量");
    static Value s_value;

    Father()
    {
        cout << "Father类的构造函数" << endl;
    }

    ~Father()
    {
        cout << "Father类的析构函数" << endl;
    }
};

Value Father::s_value = Value("Father类的静态成员变量");

class Son:public Father
{
public:
    Value value2 = Value("Son类的成员变量");
    static Value s_value2;

    Son():Father()
    {
        cout << "Son类的构造函数" << endl;
    }

    ~Son()
    {
        cout << "Son类的析构函数" << endl;
    }
};

Value Son::s_value2 = Value("Son类的静态成员变量");


int main()
{
    cout << "程序开始执行" << endl;
    {
        Son s;
        cout << "对象s使用中......" << endl;
    }

    cout << "程序结束执行" << endl;
    return 0;
}

4.3.4.多重继承

即一个派生类可以有多个基类

#include <iostream>

using namespace std;

class Sofa
{
public:
    void sit()
    {
        cout << "能坐着!" << endl;
    }
};

class Bed
{
public:
    void lay()
    {
        cout << "能躺着!" << endl;
    }
};

class SofaBed:public Sofa,public Bed
{

};

int main()
{
    SofaBed sb;
    sb.sit();
    sb.lay();

    return 0;
}

4.3.4.1二义性问题

  1. 多个基类拥有重名的成员时,会出现二义性
  2. 菱形继承,如果一个派生类的多个基类拥有共同的基类,这种情况就是菱形继承(通过虚继承实现)

#include <iostream>

using namespace std;

class Furniture // 家具
{
public:
    void show()
    {
        cout << "这是个家具" << endl;
    }
};

class Bed:public Furniture // 床
{

};

class Sofa:public Furniture // 沙发
{

};

class SofaBed:public Bed,public Sofa // 沙发床
{

};

int main()
{
    SofaBed sb;
//    sb.show(); 错误:二义性

    // 告诉编译器用哪个基类的代码
    sb.Bed::show();
    sb.Sofa::show();
//    sb.Furniture::show(); 错误:二义性


    return 0;
}

#include <iostream>

using namespace std;

class Furniture // 家具
{
public:
    void show()
    {
        cout << "这是个家具" << endl;
    }
};

class Bed:virtual public Furniture // 床
{

};

class Sofa:virtual public Furniture // 沙发
{

};

class SofaBed:public Bed,public Sofa // 沙发床
{

};

int main()
{
    SofaBed sb;
    sb.show();

    return 0;
}

4.3.5权限的继承

公有继承:派生类可以继承基类所有权限的成员,但是无法直接访问基类的私有成员,对于基类的保护成员与公有成员,在派生类中仍然是原来的权限。
保护继承:派生类可以继承基类所有权限的成员,但是无法直接访问基类的私有成员,对于基类的保护成员与公有成员,在派生类中都变为保护权限。
私有继承:派生类可以继承基类所有权限的成员,但是无法直接访问基类的私有成员,对于基类的保护成员与公有成员,在派生类中都变为私有权限。

#include <iostream>

using namespace std;

class Father
{
private:
    string str1 = "Father的私有权限";

protected:
    string str2 = "Father的保护权限";

public:
    string str3 = "Father的公有权限";
};

class Son:public Father
{


};

class Grandson:public Son
{
public:
    void test()
    {
        cout << str2 << endl;
        cout << str3 << endl;
    }
};

int main()
{
    Son s;
//    cout << s.str2 << endl; 错误
    cout << s.str3 << endl;

    Grandson gs;
    gs.test();

    return 0;
}

#include <iostream>

using namespace std;

class Father
{
private:
    string str1 = "Father的私有权限";

protected:
    string str2 = "Father的保护权限";

public:
    string str3 = "Father的公有权限";
};

class Son:protected Father
{


};

class Grandson:public Son
{
public:
    void test()
    {
        cout << str2 << endl;
        cout << str3 << endl;
    }
};

int main()
{
    Son s;
//    cout << s.str2 << endl; 错误
//    cout << s.str3 << endl; 错误

    Grandson gs;
    gs.test();

    return 0;
}

#include <iostream>

using namespace std;

class Father
{
private:
    string str1 = "Father的私有权限";

protected:
    string str2 = "Father的保护权限";

public:
    string str3 = "Father的公有权限";
};

class Son:private Father
{
public:
    void test()
    {
        cout << str2 << endl;
        cout << str3 << endl;
    }
};

class Grandson:public Son
{
public:
    void test()
    {
        //        cout << str2 << endl; 错误
        //        cout << str3 << endl; 错误
    }
};

int main()
{
    Son s;
    //    cout << s.str2 << endl; 错误
    //    cout << s.str3 << endl; 错误

    Grandson gs;
    gs.test();
    s.test();

    return 0;
}

4.4多态的体现

多态可以理解为只写一个函数接口,在程序运行时才决定调用类型对应的代码。
多态的使用,需要使用以下条件:

  1. 必须使用公有继承
  2. 派生类要有函数覆盖
  3. 基类引用/指针指向派生类对象

4.4.1函数覆盖

函数覆盖与函数隐藏很相似(函数隐藏就是重名),区别在与基类被覆盖的函数需要设置为虚函数。
虚函数有以下特点:

  1. 虚函数具有传递性,基类使用,派生类覆盖函数会自动生成虚函数
  2. 只有非静态成员函数与析构函数可以被定义为虚函数
  3. 如果声明定义分离,只需要使用virtual关键字修饰函数声明处
#include <iostream>

using namespace std;

class Animal
{
public:
    virtual void eat() // 虚函数
    {
        cout << "动物吃东西" << endl;
    }
};

class Cat:public Animal
{
public:
    void eat() // 虚函数
    {
        cout << "猫吃鱼" << endl;
    }
};

class Dog:public Animal
{
public:
    void eat() // 虚函数
    {
        cout << "狗吃骨头" << endl;
    }
};

4.4.2使用多态

多态通常搭配函数参数使用,分别写两个函数,触发引用和指针类型的多态

#include <iostream>

using namespace std;

class Animal
{
public:
    virtual void eat() // 虚函数
    {
        cout << "动物吃东西" << endl;
    }
};

class Cat:public Animal
{
public:
    void eat() // 虚函数
    {
        cout << "猫吃鱼" << endl;
    }
};

class Dog:public Animal
{
public:
    void eat() // 虚函数
    {
        cout << "狗吃骨头" << endl;
    }
};

// 引用多态
void test_eat1(Animal& a)
{
    a.eat();
}

void test_eat2(Animal* a)
{
    a->eat();
}

int main()
{
    Animal a1;
    Cat c1;
    Dog d1;
    // 测试引用多态效果
    test_eat1(a1); // 动物吃东西
    test_eat1(c1); // 猫吃鱼
    test_eat1(d1); // 狗吃骨头

    Animal* a2 = new Animal;
    Cat* c2 = new Cat;
    Dog* d2 = new Dog;
    test_eat2(a2); // 动物吃东西
    test_eat2(c2); // 猫吃鱼
    test_eat2(d2); // 狗吃骨头

    return 0;
}

4.4.2虚析构函数

通过基类引用或指针指向派生类对象,当使用delete销毁对象时,只会调用基类的析构函数,不会调用派生类的析构函数,此时,如果派生类中有new申请内存资源,那么会造成内存泄漏问题

#include <iostream>

using namespace std;

class Animal
{
public:
    virtual ~Animal()
    {
        cout << "基类的析构函数" << endl;
    }
};
class Dog:public Animal
{
public:
    ~Dog()
    {
        cout << "派生类的析构函数" << endl;
    }
};

int main()
{
    Animal* a = new Dog;
    delete a; //派生类的析构函数 基类的析构函数
    return 0;
}


因此在设计一个类时,如果这个类会成为其他类的基类,哪怕析构函数什么都不写,也要手写空的析构函数并加上virtual关键字修饰,除非可以确定此类不会被任何类继承。

5.static关键字

5.1静态成员变量

特点:

  1. 需要类内声明,类外定义
  2. 所有对象共用一份,非静态的对象各持有一份
  3. 可以直接类名调用,无需成员调用
  4. 运行时就开辟了,结束运行自动回收
  5. this可以调用静态成员(静态成员变量和静态成员函数)
#include <iostream>

using namespace std;

class Test
{
public:
    string str1 = "非静态成员变量";
    static string str2; // 类内只声明
    static const int a = 1; // 【特例】const修饰的静态成员变量可以类内初始化

    void function() // 非静态的成员函数
    {
        // 为了方便理解,加入this指针,实际编写的过程中可取消
        cout << this->str1 << endl;
        cout << this->str2 << endl;
    }
};

// 类外初始化
string Test::str2 = "静态成员变量";

int main()
{
    // 直接使用类名调用
    cout << Test::str2 << " " << &Test::str2 << endl; // 静态成员变量 0x40b038

    Test t1;
    cout << t1.str1 << " " << &t1.str1 << endl; // 非静态成员变量 0x61fe8c
    cout << t1.str2 << " " << &t1.str2<< endl; // 静态成员变量 0x40b038

    Test t2;
    cout << t2.str1 << " " << &t2.str1<< endl; // 非静态成员变量 0x61fe88
    cout << t2.str2 << " " << &t2.str2<< endl; // 静态成员变量 0x40b038

    t1.function(); // 非静态成员变量\n静态成员变量

    return 0;
}

5.2静态成员函数

#include <iostream>

using namespace std;

class Test
{
private:
    string str1 = "非静态成员";
    static string str2;

public:
    void function1()
    {
        cout << "这是一个非静态成员函数:";
        cout << str1;
        cout << str2;
        cout << endl;
    }

    static void function2()
    {
        cout << "这是一个静态成员函数:";
//        cout << str1; 错误
        cout << str2;
        cout << endl;
    }
};

string Test::str2 = "静态成员";

int main()
{
    Test::function2();

    Test t;
    t.function1();
    t.function2(); // 也能通过对象,虽然不建议

    return 0;
}

5.3静态局部变量

第一次被调用的时候,就开辟空间,结束后,不会销毁,下次被调用时,再次使用之前的静态局部变量。直到程序运行终止才会自动销毁


#include <iostream>

using namespace std;

class Test
{
public:
    void func1()
    {
        int a = 1; // 非静态局部变量
        cout << a++ << endl;
    }

    void func2()
    {
        static int a = 1; // 静态局部变量
        cout << a++ << endl;
    }
};


int main()
{
    Test t1;
    Test t2;

    t1.func1(); // 1
    t1.func1(); // 1
    t2.func1(); // 1

    cout << "---------------" << endl;

    t1.func2(); // 1
    t1.func2(); // 2
    t2.func2(); // 3

    return 0;
}

6.const关键字

6.1.修饰成员函数

修饰成员函数,称为常成员函数,特点:无法修改属性值,无法调用非const修饰的成员函数

#include <iostream>

using namespace std;

class Car
{
private:
    string brand;

public:
    Car(string brand):brand(brand){}

    string get_brand() const
    {
//        set_brand("奔驰"); 错误
//        brand = "宝马"; 错误
//        show(); 错误
        show2();
        return brand;
    }

    void set_brand(string brand)
    {
        this->brand = brand;
    }

    void show()
    {
        cout << "滴滴滴" << endl;
    }

    void show2() const
    {
        cout << "哒哒哒" << endl;
    }
};

int main()
{
    Car c("奥迪");
    c.set_brand("大众");
    cout << c.get_brand() << endl;

    return 0;
}

6.2.修饰对象

修饰对象,表示该对象是一个常量对象。常量对象属性值不可以改,不能调用共非const成员函数

#include <iostream>

using namespace std;

class Car
{
private:
    string brand;

public:
    string model = "这个变量仅用于举例,就不封装了";

    Car(string brand):brand(brand){}

    string get_brand() const
    {
//        set_brand("奔驰"); 错误
//        brand = "宝马"; 错误
//        show(); 错误
        show2();
        return brand;
    }

    void set_brand(string brand)
    {
        this->brand = brand;
    }

    void show()
    {
        cout << "滴滴滴" << endl;
    }

    void show2() const
    {
        cout << "哒哒哒" << endl;
    }
};


int main()
{
    // const两个位置都可以
    const Car c1("奥迪");
    Car const c2("凯迪拉克");

//    c1.set_brand("大众"); 错误
    cout << c1.get_brand() << endl;
//    c1.show(); 错误
    c1.show2();
//    c1.model = "A4"; 错误
    cout << c1.model << endl;

    return 0;
}

6.3.修饰成员变量

修饰后,表示常成员变量,出生在设定后,就不能在运行期间发生变化

#include <iostream>

using namespace std;

class Person
{
private:
    const string name; // 常成员变量

public:
    const int age = 1; // 赋予初始值方式二

    Person():name("张三"){} //赋予初始值方式一(推荐)

    // 重载的构造函数
    Person(string name,int age):name(name),age(age){}

    void set_name(string name)
    {
//        this->name = name; 错误
    }

    string get_name() const
    {
        return name;
    }
};


int main()
{
    Person p;
    cout << p.get_name() << endl;
//    p.age++; 错误
    cout << p.age << endl;

    Person p2("李四",18);
    cout << p2.age << endl; // age:18

    return 0;
}

6.4.修饰局部变量

表示局部变量不可变,通常用于引用类型的函数参数

#include <iostream>

using namespace std;


void show(const int& a,const int& b)
{
    cout << a << b << endl;
}

int main()
{
    int a = 1;
    int b = 2;
    show(a,b); // 12

    return 0;
}

7.运算符重载

7.1友元

友元可以突破封装性的权限限制,随意访问类中任意部分。可以分为:友元函数,友元类,友元成员函数
注意点:

  1. 运算符重载只能在C++已有的运算符范围内,不能创建新的运算符。
  2. 运算符重载本质上也是函数重载。
  3. 重载之后的运算符无法改变原有的优先级和结合性,也不能改变运算符的操作数和语法结构
  4. 运算符重载的参数必须包含自定义数据类型,无法重载基本类型的运算符规则。
  5. 运算符重载尽量符合原功能定义。
  6. 运算符重载的参数不支持默认值的设定。
  7. 一般情况下,建议单目运算符使用成员函数重载,双目运算符使用友元函数重载

7.1.1友元函数

特点:

  1. 不属于任何一个类
  2. 没有this指针,突破权限需要对象
  3. 友元函数的声明可以放在类中任何位置,包括private
  4. 一个友元函数可以是多个类的友元函数,只需要各个类分别声明。
#include 

using namespace std;

class Job
{
private:
int income;

public:
Job(int income):income(income)
{
    cout << &this->income << endl;
}

// “声明”友元函数
friend void test_friend(Job&);
};

void test_friend(Job& j)
{
    // 尝试访问私有成员
    cout << ++j.income << " " << &j.income << endl;
}

int main()
{
    Job j1(20000); // 0x61fe8c
    test_friend(j1); // 20001 0x61fe8c

    return 0;
}

7.1.2友元类

当一个类B成为另外一个类A的友元类的时候,类A的所有成员都可以被B类访问
注意点:

  1. 友元关系与继承无关
  2. 友元关系是单向的,不具有交换性
  3. 友元关系不具有传递性
#include <iostream>

using namespace std;

class A
{
private:
    int value = 1;

public:
    int get_value() const
    {
        return value;
    }

    // “声明”友元关系
    friend class B;
};

class B
{
public:
    void test_friend(A& a)
    {
        cout << ++a.value << endl;
    }

    void test_friend2(A& a,int v)
    {
        a.value += v;
    }
};


int main()
{
    A a;
    B b;
    b.test_friend(a); // 2
    cout << a.get_value() << endl; // 2
    b.test_friend2(a,100);
    cout << a.get_value() << endl; // 102

    return 0;
}

7.1.3友元成员函数

#include <iostream>

using namespace std;

// 第三步:提前声明类A
class A;

// 第二步:因为友元用到了类B,补充类B和函数声明。
class B
{
public:
    void func(A& a);
};

// 第一步:确定友元的函数格式并“声明”
class A
{
private:
    string str = "这是类A私有的成员!";

    // 友元关系
    friend void B::func(A& a);
};

// 第四步:定义友元成员函数的内容
void B::func(A &a)
{
    cout << a.str.append("哈哈哈") << endl;
}


int main()
{
    A a;
    B b;
    b.func(a); // 这是类A私有的成员!哈哈哈

    return 0;
}

7.2.重载


可以把运算符看作是一个函数,给已有运算符赋新的功能,完成特定的操作,就需要运算符重载。运算符重载有2种重载方式,友元函数运算符重载,成员函数运算符重载

7.2.1友元函数运算符重载

#include <iostream>

using namespace std;

/**
 * @brief 自定义整数类型
 */
class Integer
{
private:
    int value;

public:
    Integer(int value):value(value){}

    int get_value() const
    {
        return value;
    }

    // “声明”友元关系
    friend Integer operator +(const Integer& i1,const Integer& i2);
    friend Integer operator ++(Integer& i); // 前置
    friend Integer operator ++(Integer& i,int); // 后置
};

Integer operator +(const Integer& i1,const Integer& i2)
{
    return i1.value + i2.value;
}

Integer operator ++(Integer& i)
{
    return ++i.value;
}

Integer operator ++(Integer& i,int)
{
    return i.value++;
}

int main()
{
    Integer i1(1);
    cout << (++i1).get_value() << endl; // 2

    Integer i2(2);
    Integer i3 = i1+i2;
    cout << (i3++).get_value() << endl; // 4
    cout << i3.get_value() << endl; // 5

    return 0;
}

7.2.2成员函数运算符重载

区别在于,是成员函数,第一个操作数使用this指针表示
注意:下面两个必须使用成员函数重载

  • 赋值运算符重载
  • 类型转换运算符重载

#include <iostream>

using namespace std;

/**
 * @brief 自定义整数类型
 */
class Integer
{
private:
    int value;

public:
    Integer(int value):value(value){}

    int get_value() const
    {
        return value;
    }

    // 成员函数运算符重载
    Integer operator +(const Integer& i); // 类内声明
    Integer operator ++(); // 前置++
    Integer operator ++(int); // 后置++
};

// 类外定义
Integer Integer::operator +(const Integer& i)
{
    return this->value+i.value;
}

Integer Integer::operator ++()
{
    return ++this->value;
}

Integer Integer::operator ++(int)
{
    return this->value++;
}

int main()
{
    Integer i1(1);
    cout << (++i1).get_value() << endl; // 2

    Integer i2(2);
    Integer i3 = i1+i2;
    cout << (i3++).get_value() << endl; // 4
    cout << i3.get_value() << endl; // 5

    return 0;
}

#include <iostream>

using namespace std;

class City
{
private:
    string name;

public:
    City(string name):name(name){}

    string get_name() const
    {
        return name;
    }

    // 以下代码写不写都一样
    City& operator =(const City& c)
    {
        cout << "赋值运算符重载" << endl; // 默认无这行代码
        this->name = c.name;
        return *this;
    }
};

int main()
{
    City c1("济南");
    City c2("青岛");
    // 赋值运算符
    cout << (c2 = c1).get_name() << endl; // 济南

    return 0;
}

#include <iostream>

using namespace std;

class Test
{
public:
    // 类型转换运算符重载
    operator int()
    {
        return 123;
    }
};

int main()
{
    Test t;
    int a = t; // 类型转换
    cout << a << endl;

    return 0;
}

8.模板

使函数或声明为一种通用类型,这种编程方式称为泛型编程。

8.1.函数模板

#include <iostream>

using namespace std;

template <class T> // 声明模板的通用数据类型T
T add(T a,T b)
{
    return a+b;
}

int main()
{
    // 运行时决定具体类型的计算方式
    cout << add(2,3) << endl; // 5
    cout << add(2.2,3.3) << endl; // 5.5
    string s1 = "AAA";
    string s2 = "BBB";
    cout << add(s1,s2) << endl; // AAABBB

    // const char* 不支持加法
    //    cout << add("111","222") << endl; 错误

    return 0;
}

8.2.类模板

#include <iostream>

using namespace std;

template <typename T> // typename可与class关键字替换
class Test
{
private:
    T value;

public:
    Test(T v):value(v){}

    T get_value() const
    {
        return value;
    }
};

class Projector // 投影仪
{
public:
    void show()
    {
        cout << "投影仪播放内容中..." << endl;
    }
};


int main()
{
    Test<int> t1(123);
    cout << t1.get_value() << endl; //123

    Projector p;
    Test<Projector> t2(p);
    t2.get_value().show(); // 投影仪播放内容中...

    return 0;
}

#include <iostream>

using namespace std;

template <typename T> // typename可与class关键字替换
class Test
{
private:
    T value;

public:
    Test(T v);

    T get_value() const;
};

template <typename T>
Test<T>::Test(T v)
{
    value = v;
}

template <typename T>
T Test<T>::get_value() const
{
    return value;
}

class Projector // 投影仪
{
public:
    void show()
    {
        cout << "投影仪播放内容中..." << endl;
    }
};


int main()
{
   
    cout << t1.get_value() << endl; //123

    Projector p;
    Test<Projector> t2(p);
    t2.get_value().show(); // 投影仪播放内容中...

    return 0;
}

9.容器

容器是用来存放数据元素的集合。容器分为顺序容器,关联容器

9.1顺序容器

9.1.1array数组

#include <iostream>
#include <array> // 容器类都需要引入头文件

using namespace std;

int main()
{
    // 创建一个长度为5的数组对象
    array<int,5> arr = {1,2,3};

    // 前三个元素的值是1,2,3
    cout << arr[0] << endl; // 1
    // 默认值0(不同的编译环境可能有所区别)
    cout << arr[3] << endl; // 0

    // 修改第四个元素的值
    arr[3] = 888;
    cout << arr[3] << endl; // 888
    // 也支持at函数(推荐)
    cout << arr.at(3) << endl; // 888

    cout << "------普通for循环-------" << endl;
    for(int i = 0;i<arr.size();i++)
    {
        cout << arr.at(i) << " ";
    }
    cout << endl;

    cout << "------for each循环-------" << endl;
    for(int i:arr)
    {
        cout << i << " ";
    }
    cout << endl;
    
	cout << "------迭代器------" << endl;
    arr<int,5>::iterator iter;//iterator 迭代器类型为读写,只是读为const_iterator
	for(iter=arr.begin();iter!=arr.end();iter++)
    {
    	cout <<*iter << " ";  
    }
    return 0;
}

9.1.2vector向量

#include <iostream>
#include <vector> // 容器类都需要引入头文件

using namespace std;

int main()
{
    // 创建初始元素为5的向量对象
    vector<int> vec(5);
    cout << vec.size() << endl; // 5
    // 可以使用[]或at函数取出元素,推荐后者
    cout << vec.at(0) << endl; // 0
    // 判断是否为空
    cout << vec.empty() << endl; // 0
    // 尾部追加
    vec.push_back(8);
    cout << vec.at(vec.size()-1) << endl; // 8
    // 在第一个位置插入一个元素
    // 参数1:插入位置,begin函数返回一个迭代器指针,指向第一个元素
    // 参数2:插入内容
    vec.insert(vec.begin(),1);
    //  在倒数第二个位置插入一个元素
    // 参数1:end函数返回一个迭代器指针,指向最后一个元素的后面
    // 参数2:插入内容
    vec.insert(vec.end()-1,678);
    // 修改第二个元素
    vec[1] = 2;
    // 删除第二个元素
    vec.erase(vec.begin()+1);
    // 删除倒数第二个元素
    vec.erase(vec.end()-2);

    cout << "------普通for循环-------" << endl;
    for(int i = 0;i<vec.size();i++)
    {
        cout << vec.at(i) << " ";
    }
    cout << endl;

    vec.clear(); // 清空

    cout << "------for each循环-------" << endl;
    for(int i:vec)
    {
        cout << i << " ";
    }
    cout << endl;
    
    cout << "------迭代器------" << endl;
    vector<int>::const_iterator iter;//iterator 迭代器类型为读写,只是读为const_iterator
	for(iter=vec.begin();iter!=vec.end();iter++)
    {
    	cout <<*iter << " ";  
    }

    return 0;
}

9.1.3list列表

#include <iostream>
#include <list> // 容器类都需要引入头文件

using namespace std;

int main()
{
    // 创建一个元素是4个hello的列表对象
    list<string> lis(4,"hello");
    // 判断是否为空
    cout << lis.empty() << endl; // 0
    // 向后追加元素
    lis.push_back("bye");
    // 头插
    lis.push_front("hi");
    // 在第二个位置插入元素“second”
    // 注意:迭代器指针不支持+运算,支持++运算
    lis.insert(++lis.begin(),"second");
    // 在倒数第二个位置插入元素"aaa"
    // 注意:迭代器指针不支持-运算,支持--运算
    lis.insert(--lis.end(),"aaa");

    // 到第5个位置插入元素“555”
    // 1. 先拿到第一个元素位置的迭代器指针
    list<string>::iterator iter =  lis.begin();
    // 2. 向后移动4位
    // 参数1:迭代器指针
    // 参数2:向后移动的数量,负数为向前
    advance(iter,4);
    // 3. 插入元素“555”
    lis.insert(iter,"555");

    // 修改第五个元素为“666”,【重新获取】并移动迭代器指针
    iter = lis.begin();
    advance(iter,4);
    *iter = "666";

    // 删除第一个元素
    lis.pop_front();
    // 删除最后一个元素
    lis.pop_back();
    // 删除第四个元素
    iter = lis.begin();
    advance(iter,3);
    lis.erase(iter);

    // 取出第一个和最后一个元素
    cout << lis.front() << " " << lis.back() << endl;

    // 不支持普通for循环遍历

    cout << "------for each循环-------" << endl;
    for(string i:lis)
    {
        cout << i << " ";
    }
    cout << endl;

      cout << "------迭代器------" << endl;
    list<string>::const_iterator iter;//iterator 迭代器类型为读写,只是读为const_iterator
	for(iter=lis.begin();iter!=lis.end();iter++)
    {
    	cout <<*iter << " ";  
    }


    return 0;
}

9.1.4deque队列

从API上兼容vector和list,从性能上位于vector和list之间

9.2.关联容器

9.2.1map键值对

元素以键值对的方式存储,键必须具有唯一性,值可以重复;键通常是字符串类型,而值可以是任何类型。

#include <iostream>
#include <map> // 容器类都需要引入头文件

using namespace std;

int main()
{
    // 创建一个map对象,尖括号中分别为键与值的类型
    map<string,int> map1;

    // 插入数据
    map1["height"] = 177;
    map1["weight"] = 80;
    map1["age"] = 25;
    map1.insert(pair<string,int>("salary",12000));

    // 取出元素
    cout << map1["salary"] << endl; // 12000
    // 更推荐使用at函数
    cout << map1.at("age") << endl; // 25

    // 修改元素
    map1["weight"] = 70;
    cout << map1["weight"] << endl;

    // 删除元素
    map1.erase("height");
    // 判断"身高"键值存不存在
    if(map1.find("age") != map1.end()) // 在
    {
        cout << "键值对存在!" << endl;
    }else // 不在
    {
        cout << "键值对不存在!" << endl;
    }

    cout << map1.size() << endl;
    // 清空
    map1.clear();
    cout << map1.size() << endl; // 0


    cout << "------迭代器------" << endl;
    map<string,int>::const_iterator iter;
    for(iter=map1.begin();iter!=map1.end();iter++)
        {
            
        	// 键使用first表示
            // 值使用second表示
        	cout << iter->first << " " << iter->second << endl;
        }

    cout << "主函数直接完毕" << endl;
    return 0;
}

10.抽象类

如果我们的基类只表达一些抽象的概念,并不与具体的对象相联系,他可以为派生类提供一个框架,这就是抽象类。
如果一个类有至少一个纯虚函数,则这个类是抽象类。
如果一个类时抽象类,则这个类至少也有一个纯虚函数。
纯虚函数是一种特殊的虚函数,纯虚函数只有声明,没有定义。

抽象类的析构函数都应该写为虚析构函数。
#include <iostream>

using namespace std;

class Shape
{
public:
    //纯虚函数
    virtual void perimeter() = 0;
    virtual void area() = 0;
    virtual ~Shape(){}
};

int main()
{
//    Shape s; //错误
    return 0;
}

10.1抽象类的使用

使用派生类实例化所有虚函数,此时,所有的派生类都会变成一个普通的类。就可以实例化了。

#include <iostream>

using namespace std;
//形状类
class Shape
{
public:
    //纯虚函数
    virtual void perimeter() = 0;
    virtual void area() = 0;
    virtual ~Shape()
    {

    }
};
//圆形类
class Circle:public Shape
{
public:
    void perimeter()
    {
        cout << "周长:2πR" << endl;
    }
    void area()
    {
        cout << "面积:πR^2" << endl;
    }
};

int main()
{
    Circle c;
    c.perimeter();
    c.area();
    return 0;
}

如果没有实例化完,还需要再实例化完为止

#include <iostream>

using namespace std;

class Shape
{
public:
    //纯虚函数
    virtual void perimeter() = 0;
    virtual void area() = 0;
    virtual ~Shape()
    {

    }
};
//多边形类
class Polygon:public Shape
{
public:
    void perimeter()
    {
        cout << "周长:∑边长" << endl;
    }

};
//矩形类
class Rectangle:public Polygon
{
public:
    void area()
    {
        cout << "面积" << endl;
    }
};

int main()
{
    //    Polygon pg; 还是抽象类,只实现了部分纯虚函数
    Rectangle ra;
    ra.perimeter();
    ra.area();
    return 0;
}

11.字符串类

#include <iostream>

using namespace std;

int main()
{
    //创建一个内容为空的字符串对象
    string s;
    //判断字符串是否为空:empty()
    cout << s.empty() << endl; //1
    string s1 = "Monday"; //隐式调用构造函数
    string s2("Monady"); //显示调用构造函数
    cout << (s1==s2) << endl; //0
    string s3(s2); //拷贝构造函数
    s2 = s1; //赋值运算符
    cout << s3 << " " << s2 << endl; //Monady Monday

    //参数1:char*原字符串 参数2:从前往后保留字符数
    string s4("ASDFF",2);
    cout << s4 << endl; //AS
    //参数1:string原字符串 参数2:从前往后不保留的一个字符数
    string s5(s2,2);
    cout << s5 << endl; //nday

    //参数1:字符数量 参数2:char字符内存
    string s6(5,'A');
    cout << s6 << endl; //AAAAA

    //交换字符串--swap()
    swap(s5,s6);
    cout << s5 << " " << s6 << endl; //AAAAA nday

    //字符串连接:+
    string s7 = s4 + s6;
    cout << s7 << endl; //ASnday

    //向后追加字符串:append()
    s7.append("YYY").append("WUW");
    cout << s7 << endl; //ASndayYYYWUW

    //向后追加单字符:push_back()
    s7.push_back('p');
    cout << s7 << endl; //ASndayYYYWUWp

    //插入字符串:insert()  参数1:插入的位置 参数2:插入的内容
    s7.insert(2,"******");
    cout << s7 << endl; //AS******ndayYYYWUWp

    //删除:erase() 参数1:删除的起始位置 参数2:删除的字符数量
    s7.erase(4,3);
    cout << s7 << endl; //AS***ndayYYYWUWp

    //替换:replace() 参数1:替换的起始位置  参数2:替换的字符数量  参数3:替换的新内容
    s7.replace(0,3,"====");
    cout << s7 << endl; //====**ndayYYYWUWp

    //清空:clear()
    s7.clear();
    cout << s7.size() << endl; //0

    return 0;
}

热门相关:有个人爱你很久   豪门重生盛世闲女   名门天后:重生国民千金      妈妈丰满的朋友