C++基础

一、理论

           1、虚函数

      1.1、定义:​       

          虚函数就是在类中被关键字virtual声明的函数,一般只在基类中声明虚函数。

          1.2、规则:

                                      1、虚函数必须是类的成员函数,不能是类的静态成员函数或友元函数,因为虚函数的调用要靠特定的对象来决定该激活哪个函数

                                      2、在派生类中需要定义与虚函数同名的函数(完全相同:函数类型、函数名、参数个数、参数类型顺序)

                                      3、类的构造函数不能是虚函数。因为虚函数是为了实现多态性,根据不同的对象在运行过程中才决定与哪个函数建立联系,而构造函数在对象创建时自动运行

                                     4、析构函数可以声明为虚函数,且通常都是被声明为虚函数

                                     5、基类的虚函数无论被公有继承多少次,在多级派生中仍然为虚函数(虽然派生类中可以省略virtual,但是为了能一眼辨认,建议不要省略)

                                     6、必须使用基类指针访问虚函数,才能获得运行的多态性。虽然可以使用对象名和点运算符(面向对象的方式)的方式来调用虚函数,但是这种调用是在编译时进行的,是静态联编,不能利用虚函数的好处

    1.3、作用:​

                                    允许通过基类类型的 指针或引用 来访问派生类中的同名函数。

                 

动态多态引出虚函数

例1

#include<iostream>
using namespace std;
class Animal
{
    public:
        //虚函数    父类的virtual必须写
        virtual void Speak()                //增加了个 virtual
        {
            cout<<"动物在说话"<<endl;
        }
}; 
class Cat : public Animal
{
    public:
            //重写   函数返回值类型  函数名 参数列表 完全相同
        (virtual) void Speak()        //对于子类 virtual可写可不写
        {
            cout<<""<<endl;
        }
};
class Dog : public Animal
{
    public:
        void Speak()
        {
            cout<<""<<endl;
        }
};
void doSpeak(Animal &animal)    //父类的引用指向子类对象
{
    animal.Speak();        //这个Speak拥有了多种形态,根据传入的对象不同,来表示调用函                        数的不同
}
void test()
{
    Cat cat;
    doSpeak(cat);
    
    Dog dog;
    doSpeak(dog);
}
int main()
{
    test();
    return 0;
} 

例二:

#include<iostream>
using namespace std;
class B1
{
    public:
        virtual void Display()
        {
            cout<<"基类B1"<<endl; 
        }
};
class B2
{
    public:
        virtual void Display()
        {
            cout<<"基类B2"<<endl;    
        }    
}; 
class B3 : public B1 ,public B2
{
    void Display()
    {
        cout<<"基类B3"<<endl;
    }
}; 
int main()
{
    B1 *b1,bb1;
    B2 *b2,bb2;
    B3 *b3,bb3;
    b1 = &bb1,b1 -> Display();
    
    b2 = &bb2,b2 -> Display();
    
    b1 = &bb3,b1 -> Display();
    
    b2 = &bb3,b2 -> Display();
    return 0;
} 

           2、纯虚函数

                          2.1、定义:

                              基类往往表示一种没有具体意义的抽象概念(或表示共性,但后代在共性的基础上又有差异性),即使虚函数在基类中不需要做任何工作,也要写出一个空的函数体。

                          2.2、语法

                             virtual 函数返回值类型 虚函数名(参数列表) = 0;

                          2.3、规则:

                                    1. 纯虚函数是一种没有函数体的特殊虚函数,声明时“= 0” 表示这是这是一个纯虚函数
                                    2. 纯虚函数不能被调用,只能在派生类中具体定义后才可调用
                                    3. 纯虚函数的作用在于基类给派生类提供一个标准的函数模型,统一的接口来实现动态多态,派生类根据需要写出纯虚函数的具体代码

                

#include<iostream>
using namespace std;
class Animal
{
    protected:
        string name;
        int age;
        
    public:
        Animal(string _name,int _age):name(_name),age(_age){}
        
        virtual void Behavior() = 0;
};
class Dog:public Animal
{
    public:
        Dog(string _name,int _age):Animal(_name,_age){}        //只能用列表法继承构造函数
        
        void Behavior()
        {
            cout<<"修勾名为:"<<name<<endl;
            cout<<"年龄为:"<<age<<endl;
        }
};
class Cat:public Animal
{
    public:
        Cat(string _name,int _age):Animal(_name,_age){}
        
        void Behavior()
        {
            cout<<"修猫名为:"<<name<<endl;
            cout<<"年龄为:"<<age<<endl;
        }
};
int main()
{
    Dog d1("大黄",3);
    Cat c1("憨憨",2);
    Animal *p;            //定义父类 对象指针
    p = &d1;            //指向子类对象
    p -> Behavior();    //父类对象指针调用子类函数
    p = &c1;
    p -> Behavior();    
    return 0;
}

   3、explicit详解

    3.1、定义      

      C++中的explicit关键字只能用于修饰只有一个参数类构造函数, 它的作用是表明该构造函数是显示的, 而非隐式的, 跟它相对应的另一个关键字是implicit, 意思是隐藏的,类构造函数默认情况下即声明为implicit(隐式)。

    3.2、构造函数的隐式声明和显示声明的区别

class CxString  // 没有使用explicit关键字的类声明, 即默认为隐式声明  
{  
public:  
    char *_pstr;  
    int _size;  
    CxString(int size)  
    {  
        _size = size;                // string的预设大小  
        _pstr = malloc(size + 1);    // 分配string的内存  
        memset(_pstr, 0, size + 1);  
    }  
    CxString(const char *p)  
    {  
        int size = strlen(p);  
        _pstr = malloc(size + 1);    // 分配string的内存  
        strcpy(_pstr, p);            // 复制字符串  
        _size = strlen(_pstr);  
    }  
    // 析构函数这里不讨论, 省略...  
};  
  
    // 下面是调用:  
  
    CxString string1(24);     // 这样是OK的, 为CxString预分配24字节的大小的内存  
    CxString string2 = 10;    // 这样是OK的, 为CxString预分配10字节的大小的内存 ,在C++中, 如果的构造函数只有一个参数时, 那么在编译的时候就会有一个缺省的转换操作:将该构造函数对应数据类型的数据转换为该类对象. 也就是说 “CxString string2 = 10;” 这段代码, 编译器自动将整型转换为CxString类对象, 实际上等同于下面的操作:
    CxString string3;         // 这样是不行的, 因为没有默认构造函数, 错误为: “CxString”: 没有合适的默认构造函数可用  
    CxString string4("aaaa"); // 这样是OK的  
    CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
    CxString string6 = 'c';   // 这样也是OK的, 其实调用的是CxString(int size), 且size等于'c'的ascii码  
    string1 = 2;              // 这样也是OK的, 为CxString预分配2字节的大小的内存  
    string2 = 3;              // 这样也是OK的, 为CxString预分配3字节的大小的内存  
    string3 = string1;        // 这样也是OK的, 至少编译是没问题的, 但是如果析构函数里用

 

class CxString  // 使用关键字explicit的类声明, 显示转换  
{  
public:  
    char *_pstr;  
    int _size;  
    explicit CxString(int size)  
    {  
        _size = size;  
        // 代码同上, 省略...  
    }  
    CxString(const char *p)  
    {  
        // 代码同上, 省略...  
    }  
};  
  
    // 下面是调用:  
  
    CxString string1(24);     // 这样是OK的  
    CxString string2 = 10;    // 这样是不行的, 因为explicit关键字取消了隐式转换  
    CxString string3;         // 这样是不行的, 因为没有默认构造函数  
    CxString string4("aaaa"); // 这样是OK的  
    CxString string5 = "bbb"; // 这样也是OK的, 调用的是CxString(const char *p)  
    CxString string6 = 'c';   // 这样是不行的, 其实调用的是CxString(int size), 且size等于'c'的ascii码, 但explicit关键字取消了隐式转换  
    string1 = 2;              // 这样也是不行的, 因为取消了隐式转换  
    string2 = 3;              // 这样也是不行的, 因为取消了隐式转换  
    string3 = string1;        // 这样也是不行的, 因为取消了隐式转换, 除非类实现操作符"="的重载 

explicit关键字只对有一个参数的类构造函数有效, 如果类构造函数参数大于或等于两个时, 是不会产生隐式转换的, 所以explicit关键字也就无效了。

二、基础类

1、map

       1.1,insert如键值已存在,不更新,返回失败。

                 更新代码如下:

map<string, int>    
    
auto it = m_map.find(name);

if(it != m_map.end())
       it->second = item;
else
       m_map.insert(make_pair(name, item));

 

2、list

  2.1、遍历中删除元素

                  不能使用remove删除,需要使用erase删除。

     原因:

        1、list中remove和erase都是删除一个元素,其中remove参数类型和数据类型一致,而erase参数类型是迭代器。
        2、remove(aim)是删除链表中的aim元素,若有多个aim,都会删除。
        3、erase(it)是删除迭代器指定位置的元素,并且返回下一个位置的迭代器来看例子。

list<int> l;
for (list<int>::iterator iter=l.begin();iter!=l.end;)
{
       if (condition (*iter) == 0 )
       {      
           iter=l.erase(iter); // 注意此处要用iter接受l.erase(iter)的返回值
       }
       else
       {
            ++iter;
       }
}

 

3、vector 与 list区别

         vector 拥有一块连续的内存,因此支持随机访问,支持高效率的访问。

         list拥有一段不连续的内存空间,支持高效率的插入和删除,而不关心访问效率。

 

三、第三方库

1、pthread

1.1、pthread_cond_wait

       功能:只要到这个函数,就发生阻塞,直到使用pthread_cond_signal或者pthread_cond_broadcast给条件变量发送信号,此时该线程才继续运行。

      原文:

The pthread_cond_wait() and pthread_cond_timedwait() functions are used to block on a condition variable. They are called with mutex locked by the calling thread or undefined behaviour will result.

These functions atomically release mutex and cause the calling thread to block on the condition variable cond ; atomically here means "atomically with respect to access by another thread to the mutex and then the condition variable". That is, if another thread is able to acquire the mutex after the about-to-block thread has released it, then a subsequent call topthread_cond_signal() or pthread_cond_broadcast() in that thread behaves as if it were issued after the about-to-block thread has blocked.

 由上解释可以看出,pthread_cond_wait() 必须与pthread_mutex 配套使用。

wait的内部操作:

                          1、阻塞等待条件变量cond

                          2、释放已掌握的互斥锁(解锁互斥量)相当于ptherad_mutex_unlock(&mutex);

                                                 (1、2两步为一个原子操作)

                          3、当被唤醒,pthread_cond_wait函数返回时,解除阻塞并重新申请获取互斥锁pthread_mutex_lock(&mutex);

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <pthread.h>

//链表的结点
struct msg
{
    int num; 
    struct msg *next; 
};
 
struct msg *head = NULL;    //头指针
struct msg *temp = NULL;    //节点指针

//静态方式初始化互斥锁和条件变量
pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER;
pthread_cond_t has_producer = PTHREAD_COND_INITIALIZER;
 
void *producer(void *arg)
{
    while (1)   //线程正常不会解锁,除非收到终止信号
    {
        pthread_mutex_lock(&mutex);         //加锁

        temp = malloc(sizeof(struct msg));
        temp->num = rand() % 100 + 1;
        temp->next = head;
        head = temp;                        //头插法
        printf("---producered---%d\n", temp->num);

        pthread_mutex_unlock(&mutex);       //解锁
        pthread_cond_signal(&has_producer); //唤醒消费者线程
        usleep(rand() % 3000);              //为了使该线程放弃cpu,让结果看起来更加明显。
    }
 
    return NULL;
}
 
void *consumer(void *arg)
{
    while (1)       //线程正常不会解锁,除非收到终止信号
    {
        pthread_mutex_lock(&mutex);     //加锁
        while (head == NULL)            //如果共享区域没有数据,则解锁并等待条件变量
        {
            pthread_cond_wait(&has_producer, &mutex);   //我们通常在一个循环内使用该函数
        }
        temp = head;
        head = temp->next;
        printf("------------------consumer--%d\n", temp->num);
        free(temp);                     //删除节点,头删法
        temp = NULL;                    //防止野指针
        pthread_mutex_unlock(&mutex);   //解锁

        usleep(rand() % 3000);          //为了使该线程放弃cpu,让结果看起来更加明显。
    }
 
    return NULL;
}
 
int main(void)
{
    pthread_t ptid, ctid;
    srand(time(NULL));      //根据时间摇一个随机数种子

    //创建生产者和消费者线程
    pthread_create(&ptid, NULL, producer, NULL);
    pthread_create(&ctid, NULL, consumer, NULL);

    //主线程回收两个子线程
    pthread_join(ptid, NULL);
    pthread_join(ctid, NULL);
 
    return 0;
}

 

2、magic_enum

      2.1、作用

       枚举类型和字符串相互转换。要求C++17

      2.2、下载地址

      https://github.com/Neargye/magic_enum

      2.3、案例

#include <iostream>
#include <magic_enum.hpp>
#include <magic_enum_iostream.hpp>

enum  Color : int { RED = -10, BLUE = 0, GREEN = 10 };

int main() {
  // 枚举转字符串
  Color c1 = Color::RED;
  auto c1_name = magic_enum::enum_name(c1);
  std::cout << c1_name << std::endl; // RED

  // 枚举所有名称字符串
  constexpr auto names = magic_enum::enum_names<Color>();
  std::cout << "Color names:";
  for (const auto& n : names) {
    std::cout << " " << n;
  }
  std::cout << std::endl;
  // Color names: RED BLUE GREEN

  // 字符串转枚举
  auto c2 = magic_enum::enum_cast<Color>("BLUE");
  if (c2.has_value()) {
    std::cout << "BLUE = " << to_integer(c2.value()) << std::endl; // BLUE = 0
  }

  return 0;
}

 2.4、注意点

        枚举数值不能超过127,修改值超过修改MAGIC_ENUM_RANGE_MAX宏定义

MAGIC_ENUM_RANGE_MAX

四、设计模式

           参考文献:1、https://refactoringguru.cn/design-patterns

  1、创建型模式

    共五种:工厂方法模式、抽象工厂模式、单例模式、建造者模式、原型模式。

  2、结构型模式

    共七种:适配器模式、装饰器模式、代理模式、外观模式、桥接模式、组合模式、享元模式。

  3、行为型模式

    共十一种:策略模式、模板方法模式、观察者模式、迭代子模式、责任链模式、命令模式、备忘录模式、状态模式、访问者模式、中介者模式、解释器模式。

    3.1、观察者模式

      3.1.1、应用场景

        1、当一个对象状态的改变需要改变其他对象, 或实际对象是事先未知的或动态变化的时, 可使用观察者模式。

        2、当应用中的一些对象必须观察其他对象时, 可使用该模式。 但仅能在有限时间内或特定情况下使用。

      3.1.2、真实世界类比

        杂志和报纸订阅:

        如果你订阅了一份杂志或报纸, 那就不需要再去报摊查询新出版的刊物了。 出版社 (即应用中的 “发布者”) 会在刊物出版后 (甚至提前) 直接将最新一期寄送至你的邮箱中。

出版社负责维护订阅者列表, 了解订阅者对哪些刊物感兴趣。 当订阅者希望出版社停止寄送新一期的杂志时, 他们可随时从该列表中退出。

      3.1.3、代码


#include <iostream>
#include <list>
#include <string>
//抽象观察者 class IObserver { public: virtual ~IObserver(){}; virtual void Update(const std::string &message_from_subject) = 0; };
//抽象主题
class ISubject { public: virtual ~ISubject(){}; virtual void Attach(IObserver *observer) = 0; virtual void Detach(IObserver *observer) = 0; virtual void Notify() = 0; }; //具体主题 class Subject : public ISubject { public: virtual ~Subject() { std::cout << "Goodbye, I was the Subject.\n"; } //订阅管理方法
//订阅 void Attach(IObserver *observer) override { list_observer_.push_back(observer); }
//取消订阅
void Detach(IObserver *observer) override { list_observer_.remove(observer); }
//通知
void Notify() override { std::list<IObserver *>::iterator iterator = list_observer_.begin(); HowManyObserver(); while (iterator != list_observer_.end()) { (*iterator)->Update(message_); ++iterator; } }
//发布新消息
void CreateMessage(std::string message = "Empty") { this->message_ = message; Notify(); } private: std::list<IObserver *> list_observer_; std::string message_; };
//具体观察者
class Observer : public IObserver { public: Observer(Subject &subject) : subject_(subject) { this->subject_.Attach(this);//订阅主题 std::cout << "Hi, I'm the Observer \"" << ++Observer::static_number_ << "\".\n"; this->number_ = Observer::static_number_; } virtual ~Observer() { std::cout << "Goodbye, I was the Observer \"" << this->number_ << "\".\n"; }
//更新消息
void Update(const std::string &message_from_subject) override { message_from_subject_ = message_from_subject; PrintInfo(); }

//取消订阅
void RemoveMeFromTheList() { subject_.Detach(this); std::cout << "Observer \"" << number_ << "\" removed from the list.\n"; } void PrintInfo() { std::cout << "Observer \"" << this->number_ << "\": a new message is available --> " << this->message_from_subject_ << "\n"; } private: std::string message_from_subject_; Subject &subject_; static int static_number_; int number_; }; int Observer::static_number_ = 0; void ClientCode() { Subject *subject = new Subject; Observer *observer1 = new Observer(*subject); Observer *observer2 = new Observer(*subject); Observer *observer3 = new Observer(*subject); Observer *observer4; Observer *observer5; subject->CreateMessage("Hello World! :D"); observer3->RemoveMeFromTheList(); subject->CreateMessage("The weather is hot today! :p"); observer4 = new Observer(*subject); observer2->RemoveMeFromTheList(); observer5 = new Observer(*subject); subject->CreateMessage("My new car is great! ;)"); observer5->RemoveMeFromTheList(); observer4->RemoveMeFromTheList(); observer1->RemoveMeFromTheList(); delete observer5; delete observer4; delete observer3; delete observer2; delete observer1; delete subject; } int main() { ClientCode(); return 0; }

 

    3.2、中介者模式

      3.2.1、应用场景

        1、当对象之间的关系过于复杂,而且它们之间的依赖关系难以理解和管理时,可以考虑使用中介者模式,将这些依赖管理起来。

                             2、当一个对象需要和多个其他对象进行互动,并且这些对象之间存在复杂的互动关系时,可以考虑使用中介者模式,将它们之间的交互都集中到一个中介者对象中。

                            3、当修改一个对象会涉及到许多其他对象时,可以使用中介者模式来避免修改所有对象,只需要更新中介者即可。

                            4、当一组对象在协作过程中会产生大量相互关联的代码时,可以使用中介者模式来将其抽象出来,更好地隐藏其复杂性。

      3.2.2、真实世界类比

        飞机降落:

        飞行器驾驶员之间不会通过相互沟通来决定那一架飞机降落,所有沟通都通过控制塔台进行。

      3.2.3、代码#include <iostream>

#include <string>
/**
* 抽象中介,定义Notify函数,该函数用于传递一共组件和事件名称,中介也可以把对应的事件和组件传递给其他组件。
*/ class BaseComponent;
class Mediator { public: virtual void Notify(BaseComponent *sender, std::string event) const = 0; }; /** * 抽象组件,提供将中介实例化存储在组件对象中。 */ class BaseComponent { protected: Mediator *mediator_; public: BaseComponent(Mediator *mediator = nullptr) : mediator_(mediator) { } void set_mediator(Mediator *mediator) { this->mediator_ = mediator; } }; /** * 具体组件 */ class Component1 : public BaseComponent { public: void DoA() { std::cout << "Component 1 does A.\n"; this->mediator_->Notify(this, "A"); } void DoB() { std::cout << "Component 1 does B.\n"; this->mediator_->Notify(this, "B"); } }; class Component2 : public BaseComponent { public: void DoC() { std::cout << "Component 2 does C.\n"; this->mediator_->Notify(this, "C"); } void DoD() { std::cout << "Component 2 does D.\n"; this->mediator_->Notify(this, "D"); } }; /** * 具体中介,它协调多个组件相互协作完成任务。 */ class ConcreteMediator : public Mediator { private: Component1 *component1_; Component2 *component2_; public: ConcreteMediator(Component1 *c1, Component2 *c2) : component1_(c1), component2_(c2) { this->component1_->set_mediator(this); this->component2_->set_mediator(this); } void Notify(BaseComponent *sender, std::string event) const override { if (event == "A") { std::cout << "Mediator reacts on A and triggers following operations:\n"; this->component2_->DoC(); } if (event == "D") { std::cout << "Mediator reacts on D and triggers following operations:\n"; this->component1_->DoB(); this->component2_->DoC(); } } }; /** * The client code. */ void ClientCode() { Component1 *c1 = new Component1; Component2 *c2 = new Component2; ConcreteMediator *mediator = new ConcreteMediator(c1, c2); std::cout << "Client triggers operation A.\n"; c1->DoA(); std::cout << "\n"; std::cout << "Client triggers operation D.\n"; c2->DoD(); delete c1; delete c2; delete mediator; } int main() { ClientCode(); return 0; }

 

4、其他

共二种:并发型模式和线程池模式。

 

 
posted @ 2024-01-18 17:48  飞说晓事  阅读(60)  评论(0)    收藏  举报