C++类和对象的属性

C++类和对象的属性

千钧一发,让一根头发去承受三万斤的重量,但是它没有断。-----余华

const修饰结构体指针

内部值不能修改,即:只能读,不能写。防止误操作

#include <iostream>
using namespace std;struct Point
{int x;int y;
};int main()
{// const 修饰指针,指向的对象不能通过该指针被修改const Point* p1 = new Point{3, 4};// p1->x = 5;  // 编译错误:指针指向的对象是 const 的,不能被修改// 指针本身是 const,不能指向其他对象Point* const p2 = new Point{5, 6};// p2 = new Point{7, 8};  // 编译错误:p2 是 const 指针,不能指向其他对象p2->y = 8;  // 合法:指针指向的对象可以通过此指针被修改// 同时 const 修饰指针和指向的对象const Point* const p3 = new Point{7, 8};// p3->x = 9;  // 编译错误:指针指向的对象是 const 的,不能被修改// p3 = new Point{10, 11};  // 编译错误:p3 是 const 指针,不能指向其他对象return 0;
}

左移运算符重载

无法用成员函数写这个函数,因为不能把左移cout写在左边

#include <iostream>
using namespace std;class Point
{
public:int x_, y_;Point(int x, int y) : x_(x), y_(y) {}friend ostream &operator<<(ostream &os, const Point &p){os << p.x_ << " " << p.y_ << endl;return os;}
};int main()
{Point p(1, 2);cout<<p<<p;return 0;
}

运算符加号重载

//1、成员函数重载+号
Person operator+(Person &p){Person temp;temp.m_A = this->m_A + p.m_A;temp.m_B = this->m_B + p.m_B;return temp;
}
//2、全局函数重载+号
Person operator+(Person &p1, Person &p2){Person temp;temp.m_A = p1.m_A + p2.m_A;temp.m_B = p1.m_B + p2.m_B;return temp;
}// 成员函数调用本质
Person p3 = p1.operator+(p2);// 全局函数调用本质
Person p3 = operator+(p1, p2);

友元类技术

利用友元类技术,可以访问私有类型

#include <iostream>
using namespace std;class MyClass{
private:int a_;friend class FriendClass;
public:MyClass(int a): a_(a){}
};class FriendClass{
public:void accessPrivateMember(MyClass& my){cout<<"Myclass private menber: "<< my.a_<<endl;}};int main(){MyClass my(10);FriendClass friendClass;friendClass.accessPrivateMember(my);return 0;
}

构造函数

构造函数也可以重载

创建任何对象都要调用构造函数

析构顺序

当类内存在其他类成员时,构造函数顺序是先成员变量的构造函数,后本类的构造函数,析构顺序则相反

#include <iostream>
#include <string>
using namespace std;
class Phone;
class Person
{
public:Person(){cout << "moren" << endl;}Person(string name, int age, Phone* phone, int rea){m_name = name;m_age = age;m_phone = phone;m_rea = new int(rea);cout << "gouzao---------------" << endl;}~Person(){if (m_rea != nullptr){delete m_rea;}cout << "xigou----------------==" << endl;}Person(const Person &p){cout << "kaobei---------------===" << endl;}private:string m_name;int m_age;Phone* m_phone;//为什么使用指针,有学问在里边int *m_rea;
};class Phone
{
public:Phone(){m_Number = 1232;}Phone(int number) : m_Number(number){cout << "Phone 构造" << endl;}~Phone(){cout << "Phone 析构" << endl;}private:int m_Number;
};
void test01()
{Person p1;cout << 1 << endl;
}void test02()
{int t = 1;// Person p("123", 123, t);// Person p1(p);// cout << "02" << endl;
}void test03(){Phone phone(123);Person p("123", 123, &phone, 123);cout<<"12333333333333333333333"<<endl;
}int main()
{// test02();// cout << "03" << endl;test03();return 0;
}

上面PersonPhone成员变量使用指针是因为Person类中的Phone对象是一个不完全类型,因此编译器无法确定其大小和布局。这导致在Person类中使用Phone对象时出现错误。

拷贝构造函数

执行拷贝构造函数的三个时机

(1)、用一个已经存在的对象来创建一个新的对象时(这是最本质的原因

Spot spot2=spot1;

(2)、对象作为函数的参数(值传递,不是引用传递)时,实际上就是在调用拷贝构造函数。

Spot spot3(spot2);

(3)、函数的返回值是一个对象,执行return语句时,系统会自动调用拷贝构造函数创建返回值。

#include <iostream>
using namespace std;class MyClass {
private:int* data;
public:// 默认构造函数MyClass() {data = new int(0);cout << "Default constructor called." << endl;}// 拷贝构造函数MyClass(const MyClass& obj) {data = new int(*obj.data);  // 深拷贝cout << "Copy constructor called." << endl;}// 析构函数~MyClass() {delete data;cout << "Destructor called." << endl;}// 打印数据成员void printData() {cout << "Data: " << *data << endl;}
};// 函数参数为对象
void myFunction(MyClass obj) {obj.printData();
}MyClass myFunction1(const MyClass& obj) {return obj;
}int main() {// 用对象初始化另一个对象MyClass obj1;// MyClass obj2 = obj1;// 传递对象给函数参数// myFunction(obj1);// 通过值返回对象MyClass obj3 = myFunction1(obj1);return 0;
}

上面三个都是一次默认构造,一次拷贝构造,两次析构。myFunctionmyFunction1的区别:按值传递会触发拷贝构造函数,按引用传递不会触发,但是传递返回值时会触发拷贝构造函数,所以输出结果相同

C/C++数组名与指针区别

https://blog.csdn.net/ykm0722/article/details/7031387

静态数据成员

普通成员变量在类中只能声明可是不能定义,类在定义后并不分配存储空间,而是在定义类的对象的时候才分配存储空间,相反静态的数据成员和静态的成员函数是已经在内存中开辟了内存空间了,所以静态数据成员可以独立的访问在任何类对象没有建立起来都可以访问。

你们班里面有10个人(10个比如高一一班的对象),体育老师分给你们一个篮球(静态成员函数),你们每个人都带了一个篮球(非静态成员数),你们都很小气,自己的球只能自己拍,要是5对5打比赛,那就只能用那个静态的篮球了(每个人都可以拿来用,但是带来的影响是对全体的)。因此,我可以说那个篮球是高一一班的成员。所以也就是说:静态成员函数是类的成员函数(因为高一二班就不能拿来玩),但是这个篮球最后还是要还给老师的,任何私人不得占有。希望这样你能明白,其实在内存空间里面说白了静态的成员的内存是唯一的一份,就是当你在类外声明他时开辟的,但是非静态函数的空间分配是在你实例化对象时创建的。

静态成员变量是所有的类共有的,它有两种访问方式:

1、通过对象访问

2、通过类名访问(因为静态成员函数和静态变量是所有对象共有的一个性质)

静态成员函数调用方式:通过对象或者通过:类名::静态成员函数

静态成员函数不可以访问非静态成员变量,因为静态成员函数是所有对象共有的,当一个对象调用这个函数时,如果修改非静态成员变量,他不知道是哪个对象的变量。无法区分是哪个对象的。

静态成员函数也有访问权限

#include <iostream>
#include <string>
using namespace std;
class Person
{string name;static int Num;public:Person(string N) : name(N) { Num++; }Person(const Person& p){ Num++;}void Print() { cout << "共有" << Num << "个人" << endl; }~Person() { Num--; }
};
int Person::Num = 0;
int main()
{Person p1("hehe");Person p2 = p1;Person p3(p2);Person p4("hh");p1.Print();return 0;
}

研究静态函数成员是否有this指针

结论:没有,因为静态成员函数不属于某个对象,被类的所有对象共享。

类的组合与继承

当一个类将要被继承时,为了方便对他的继承,往往将他的private数据成员改为protected访问权限,因为基类的private成员不方便在派生类中访问

注意:只有构造函数初始化的时候能使用初始化列表。成员函数不行,要不然会报错

友元类函数

定义友元类函数,再类外部定义时不能访问private成员,而在类内定义可以,程序如下

#include <iostream>
#include <cmath>
using namespace std;
class Point
{int x;int y;public:friend float Distance(const Point &p1, const Point &p2){return sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2));}Point(const int &X, const int &Y){x = X;y = Y;}// void setX(int X) : x(X) {}
};
// float Distance(Point& p1, Point& p2)
//{
//	return sqrt(pow(p2.x - p1.x, 2) + pow(p2.y - p1.y, 2));
//}
int main()
{Point p1(10, 10), p2(11, 11);cout << Distance(p1, p2);
}                

继承

如果子类中有与父类中同名的成员函数,直接调用是调用子类中的,子类中的同名的成员函数会覆盖父类中所有同名成员函数

多继承

优缺点:

(11条消息) 多继承的概念和优缺点_HappyJandun’s 学习笔录-CSDN博客_多继承的优缺点

#include<iostream>
using namespace std;class Person
{
public:void sleep() { cout << "sleep" << endl; }void eat() { cout << "eat" << endl; }
};class Author : public Person // Author继承自Person
{
public:void writeBook() { cout << "write Book" << endl; }
};class Programmer : public Person // Programmer继承自Person
{
public:void writeCode() { cout << "write Code" << endl; }
};class Programmer_Author : public Programmer, public Author // 多重继承
{
};int main()
{Programmer_Author pa;pa.writeBook(); // 调用基类Author的方法pa.writeCode(); // 调用基类Programmer的方法// pa.eat();       // 编译错误,eat()定义不明确// pa.sleep();     // 编译错误,sleep()定义不明确return 0;
}

多重继承的优点很明显,就是对象可以调用多个基类中的接口

缺点是如果派生类所继承的多个基类有相同的基类,而派生类对象需要调用这个祖先类的接口方法,就会容易出现二义性。

通常有两个解决方案:

(1)加上全局符确定调用哪一份拷贝。比如pa.Author::eat()调用属于Author的拷贝。

(2)使用虚拟继承,使得多重继承类Programmer_Author只拥有Person类的一份拷贝。比如在11行和17行的继承语句中加入virtual就可以了。

虚函数

#include <iostream>
#include <string>
using namespace std;class Animal
{
private:string m_sound;public:Animal(string sound) : m_sound(sound) {}virtual void makeSomeSound() { cout << "Ainmal" << endl; }
};class Cat : public Animal
{
private:string m_sound;public:Cat(string sound) : m_sound(sound), Animal("animal") {} // 必须显式调用父类的有参构造函数virtual void makeSomeSound() override { cout << m_sound << endl; }
};class Dog : public Animal
{
private:string m_sound;public:Dog(string sound) : m_sound(sound), Animal("animal") {}virtual void makeSomeSound() override { cout << m_sound << endl; }
};int main()
{// 视他人之疑目如盏盏鬼火,大胆地去走你的夜路吧。Animal *animal;Dog dog("Wang Wang");Cat cat("Miao Miao");animal = &dog;animal->makeSomeSound();animal = &cat;animal->makeSomeSound();return 0;
}

编译器为了实现虚函数,通常会生成虚函数表,在程序运行时通过虚函数动态的查找,然后据此调用正确的函数,这就是动态编译。vbtable是虚基表,虚基指针(vbptr)指向虚基表。

C++为什么要用虚函数

(3条消息) C++为什么要用虚函数_Like a lunatic-CSDN博客

**总结起来:**这样做是为了当用一个基类的指针删除一个派生类的对象时,派生类的析构函数会被调用

要实现多态,需要满足以下几个条件:

  1. 子类重写(覆盖)父类的虚函数:在继承关系中,子类必须重写(覆盖)父类中的虚函数,即在子类中重新定义与父类中虚函数同名、同参数列表和同返回类型的函数,并使用关键字 “override” 明确说明这是对父类虚函数的重写。
  2. 父类指针或引用指向子类对象:通过将父类的指针或引用指向子类的对象,可以实现多态。这样,在运行时,根据指针或引用指向的对象的实际类型来确定调用的是子类中的实现。
  3. 虚函数机制:父类中被子类重写的函数必须声明为虚函数。通过虚函数表和动态绑定机制,确保在运行时根据对象的实际类型来调用正确的函数实现。

定义了虚函数,如果有三辈的继承:爷爷,父亲,儿子,在主函数中,定义一个基类指针,指针指向谁,就调用谁的函数————实现的就是动态多态

纯虚函数

一般语法

virtual void print() =0;//纯虚函数,可以有函数体,可实例化的派生类必须重写它

定义:

像这样用virtual关键字修饰,并且末尾加上 =0 的格式声明的函数就是纯虚函数,还有一个重要特征,就是在当前类里这个方法不能有具体的实现,也就是不能有方法体。同时拥有纯虚函数的类会被默认定义为抽象类。

什么是抽象类:至少拥有一个或者一个以上的纯虚函数的类,这种类不能创建对象,但是可以被继承,通常通过继承抽象类,然后通过重写纯虚函数后,方可以通过抽象父类的子类的对象来访问函数和数据成员。

模板函数的重载

#include <iostream>
using namespace std;
template <typename T>
void Test1(T a, T b)
{cout<<"Test1()"<<endl;
}
void Test1(int a, int b)//这里是重载
{cout<<"2"<<endl;
}
void Test(int a, int b)
{cout<<"3"<<endl;
}
int main()
{int a = 0, b = 1;char c = 'c', d= 'd';Test(a,b);Test1(a, c);
}

类模板

定义一个简单的Array的类模板:

#include <iostream>
using namespace std;template <class T>
class Array
{
public:Array(int size) : size_(size){data_ = new T[size];}~Array(){delete[] data_;}T &operator[](int i){return data_[i];}friend ostream &operator<<(ostream &os, const Array<T> arr){   for(int i=0; i<arr.size_; i++)cout<<arr.data_[i]<<" "; return os;}private:T *data_;int size_;
};int main()
{Array<int> a(10);for (int i = 0; i < 10; i++){a[i] = i + 1;}// for (int i = 0; i < 10; i++)cout << a << endl;return 0;
}

类模板的定义:类模板是一种通用的类的定义,通过模板可以存储不同数据类型的对象

**为什么需要类模板:**有时,有两个或多个类,其功能是相同的,仅仅是数据类型不同,如vectordequemap等的实现。

继承中的类模板:

#include <iostream>
using namespace std;template <class T>
class Base{
private:T value_;
public:Base(T value):value_(value){cout<<"Base constructed"<<endl;}};template <class T>
class Derived: public Base<T>{
private:T value_;
public:Derived(T value1, T value2):Base<T>(value1), value_(value2){cout<<"Derived constructed"<<endl;}
};int main(){Base<int> base(10);Derived<int> derived(1, 2);return 0;
}

结论: 子类从模板类继承的时候,需要让编译器知道 父类的数据类型具体是什么(数据类型的本质:固定大小内存块的别名)A

从模板类派生时,需要具体化模板类,C++编译器需要知道父类的数据类型是什么样子的 要知道父类所占的内存多少

单例模式

单例模式有许多种实现方法,

a. 懒汉式:使用的时候才创建,多线程访问的时候线程不安全(双检锁)

b. 饿汉式:类文件加载的时候已经创建好了对象,如果对象一直没有使用,则类对象浪费空间

特点与选择:

如果要进行线程同步,访问量比较大,或者可能访问的线程比较多时,采用饿汉实现,可以实现更好的性能。这是以空间换时间。

在访问量较小时,采用懒汉实现。这是以时间换空间。

CSDN博客:

C++设计模式(一)单例模式

设计原则之一:开放封闭原则:对扩展开放,对修改关闭

组合和继承的优缺点

(1)继承

继承是Is a 的关系,比如说Student继承Person,则说明Student is a Person。继承的优点是子类可以重写父类的方法来方便地实现对父类的扩展。

继承的缺点有以下几点:

①:父类的内部细节对子类是可见的。

②:子类从父类继承的方法在编译时就确定下来了,所以无法在运行期间改变从父类继承的方法的行为。

③:如果对父类的方法做了修改的话(比如增加了一个参数),则子类的方法必须做出相应的修改。所以说子类与父类是一种高耦合,违背了面向对象思想。

(2)组合

组合也就是设计类的时候把要组合的类的对象加入到该类中作为自己的成员变量

组合的优点:

①:当前对象只能通过所包含的那个对象去调用其方法,所以所包含的对象的内部细节对当前对象时不可见的。

②:当前对象与包含的对象是一个低耦合关系,如果修改包含对象的类中代码不需要修改当前对象类的代码。

③:当前对象可以在运行时动态的绑定所包含的对象。可以通过set方法给所包含对象赋值。

组合的缺点:①:容易产生过多的对象。②:为了能组合多个对象,必须仔细对接口进行定义。

const是定义为const函数的组成部分,那么就可以通过添加const实现函数重载

关于c++的常量的一点说明

我们说过,c++“真常量”只针对内置数据类型,自己定义的类,编译器没法帮你“内传折叠”,所以仍是“假常量”,是能通过指针“篡次的
另外常量是“建没性”的预防措施,并不能防蓄意破坏。

c++中的真常量只针对其内置数据类型,对于自己定义的类的,是假常量,可以通过指针修改

菱形的继承体系

class A
{
public:int a;
};
class B : virtual public A
{
public:int b;
};
class C :virtual public A
{
public:int c; 
};
class D : public B, public C
{
public:int d;
};

内存布局

上面这种菱形的继承体系中,如果没有virtual继承,那么D中就有两个A的成员int a;继承下来,使用的时候,就会有很多二义性。而加了virtual继承,在D中就只有A的成员int a;的一份拷贝,该拷贝不是来自B,也不是来自C,而是一份单独的拷贝,那么,编译器是怎么实现的呢??

在回答这个问题之前,先想一下,sizeof(A),sizeof(B),sizeof©,sizeof(D)是多少?(在32位x86的linux2.6下面,或者在vc2005下面)在linux2.6下面,结果如下:sizeof(A) = 4; sizeof(B) = 12; sizeof© = 12; sizeof(D) = 24;sizeof(B)为什么是12呢,那是因为多了一个指针(这一点和虚函数的实现一样),那个指针是干嘛的呢?

那么sizeof(D)为什么是24呢?那是因为除了继承B中的b,C中的c,A中的a,和D自己的成员d之外,还继承了B,C多出来的2个指针(B和C分别有一个)。再强调一遍,D中的int a不是来自B也不是来自C,而是另外的一份从A直接靠过来的成员。

如果声明了D的对象d: D d;

那么d的内存布局如下:

vb_ptr: 继承自B的指针

int b: 继承自B公有成员

vc_ptr:继承自C的指针

int c: 继承自C的共有成员

int d: D自己的公有成员

int a: 继承自A的公有成员

关于多态

多态分为静态多态和动态多态:

  • 静态多态是编译时发生了多态,多指函数重载和模板实现;

  • 动态多态是运行时多态,一般通过虚函数实现。

实现动态多态三个条件

1:要有public继承;

2:要有虚函数重写;

3:用父类指针(父类引用)指向子类对象

菱形继承虚基类

虚继承的出现就是为了解决重复继承中多个间接父类的问题的,二义性。钻石型的结构是其最经典的结构。

C++中的虚函数的作用主要是实现了多态的机制。关于多态,简而言之就是用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数。这种技术可以让父类的指针有“多种形态”,这是一种泛型技术。所谓泛型技术,说白了就是试图使用不变的代码来实现可变的算法。比如:模板技术,RTTI技术,虚函数技术,要么是试图做到在编译时决议,要么试图做到运行时决议

类的缺省参数:(不能在两个地方同时声明)

既可以在类的声明中,也可以在函数定义中声明缺省参数,但不能既在类声明中又在函数定义中同时声明缺省参数。

Union

#include<iostream>
using namespace std;
union myun
{struct { int x; int y; int z; }u;short k;
}a;
int main()
{a.u.x = (1<<17)+2; // 2的17次方是: 131072, 低16位是0x00 02a.u.y = 5;a.u.z = 6;a.k = 0;printf("%d %d %d %d\n", a.u.x, a.u.y, a.u.z, a.k); // a.k的16位覆盖了x的低16位printf("sizeof(a):%d\n", sizeof(a));    // 联合体的 size 是最大的数据长度:三个 int 是12cout << ((1 << 17)+2);return 0;
}
// 这里short的16位覆盖了a.u.x的前16位

我们都知道联合(也叫共用体)中的不同类型的变量都是放在同一个地址开始的内存单元里面,当然这个内存单元足够大,可以容下所占空间最大的那个数据类型

explicit关键字

在构造函数前面加上,为了防止其他类型的变量隐式转化为这个类的类型变量必须使用显式的转化

vector vs = {“hello”, “world”};比如这里就将const char转为string,但是加上explicit之后就不可以了

auto关键字的缺点

auto 不利:降低代码可读性、可能得不到你预想的类型、配合decltype有意想不到的结果

2022-4-18
// (4)
for (auto &elem:vec) // pass by referenceelem *= 3;
// (5) 编译器会把(4)转换为(5)
for (auto _pos = l.begin(), _end = l.end(); _pos != _end; ++_pos) {auto &elem = *_pos;elem *= 3;
}
auto ll1 = [](int x) -> int {return x + 10;
};
// lambda 匿名函数 的简单使用
function<int(int x)> ll = [](int x) -> int {return x + 10;
};
cout<<ll1(10)<<endl;
cout<<ll(11)<<endl;
return 0;

lambda表达式

Lambda 表达式是 C++11 引入的一个重要特性,它允许我们定义匿名函数,从而使代码更加简洁和易于阅读。

Lambda 表达式的一般形式如下:

Copy Code[capture list](parameter list) -> return type {function body}

其中:

  • capture list:捕获列表,用于指定在函数体中使用的变量。
  • parameter list:参数列表,指定函数的输入参数。
  • return type:返回类型,指定函数的返回类型(可以自动推导)。
  • function body:函数体,包含函数需要执行的代码。
#include <iostream>int main() {auto func = [] (int x, int y) -> int {return x + y;};std::cout << func(1, 2) << std::endl;  // 输出 3return 0;
}

auto 关键字

单例模式

简单单例模式

//懒汉模式:只有在第一次用到的时候才会实例化
class singleton {private:singleton() {}static singleton *p;public:static singleton *instance();
};
singleton *singleton::p = nullptr;
singleton* singleton::instance() {if (p == nullptr)p = new singleton();return p;
}
//将构造函数写在private或者protected里面,
//同时有一个static指针维护这个单例
//实例的实现由一个public方法来实现,该方法返回该类的唯一实例。
//当然这个单例模式在单线程下面是安全的,
//如果是多线程下同时检测到p是nullptr,会创建两个实例//饿汉模式
class singleton {
private:singleton() {}static singleton *p;
public:static singleton *instance();
};singleton *singleton::p = new singleton();//实例化了
singleton* singleton::instance() {return p;
}
// 当然这个是线程安全的,对于我们通常阐述的线程不安全,为懒汉模式

decltype 的意思是获得表达式的 type ,有点像 typeof

final 用在类后面 class Base final {} 表示这个类是继承的最后一个类,不能有其他类继承它

加了锁的单例模式

#include <iostream>
#include <thread>         // std::thread 
#include <mutex>          // std::mutex
using namespace std;/*用互斥量构建一个枷锁类*/
class Lock {
private:mutex mtx;//互斥量
public:void lock() { mtx.lock(); }//加锁void unlock() { mtx.unlock(); }//解锁
};
/*静态单例*/
class singleton {
private:int _value;//测试用值,静态单例在哪个线程中被第一次获取,_value的值就是哪个线程的idsingleton(int value);//构造函数singleton(const singleton&);//复制构造函数singleton& operator=(const singleton&);//赋值构造函数
public:static Lock* Locker;//声明一个枷锁指针static singleton* Instance;//声明一个对象static singleton* getInstance(int thread_id);//声明获取单例的函数int getValue();//获取测试值~singleton();//析构函数
};singleton::singleton(int value) :_value(value) {}//构造函数
singleton::singleton(const singleton&) {}//复制构造函数
singleton& singleton::operator=(const singleton&) { return *this; }//赋值构造函数
singleton::~singleton() {}//析构函数Lock* singleton::Locker = new Lock();//初始化静态单例中的枷锁
singleton* singleton::Instance = NULL;//初始化静态单例singleton* singleton::getInstance(int thread_id) {//定义获取单例的函数if (Instance == NULL) {//由于加锁比较费事,所以先判断一次Locker->lock();if (Instance == NULL) {Instance = new singleton(thread_id);//初始化静态单例,参数为 线程的id}Locker->unlock();}return Instance;
}
int singleton::getValue() {//获取测试值return _value;
}mutex myMtx;//为了防止多个线程争夺 标准输出资源而设置的互斥量
void print_thread_id(int thread_id) {//线程函数singleton* Instance = singleton::getInstance(thread_id);//获取静态单例myMtx.lock();cout << "thread_id: " << thread_id << endl;cout << "Instance is Created in thread: " << Instance->getValue() << endl;myMtx.unlock();
}int main() {thread threads[2];//两个线程int i;for (i = 0; i < 2; ++i)threads[i] = thread(print_thread_id, i);//启动线程for (auto& th : threads) th.join();//等线程结束return 0;
}
#include <iostream>
#include <thread>         // std::thread 
#include <mutex>          // std::mutex
using namespace std;
class lock12 {
private:mutex mtx;
public:void Lock() { mtx.lock(); }void unLock() { mtx.unlock(); }
};class Singleton {
private:int value;Singleton(int _value) :value(_value) {};Singleton(const Singleton& single) {};Singleton& operator=(const Singleton&) { return *this; }
public:static lock12* lock1;static Singleton* instance;static Singleton* getInstance(int thread_id);int get_value() { return value; }~Singleton() {};
};
lock12* Singleton::lock1 = new lock12();
Singleton* Singleton::instance = NULL;
Singleton* Singleton::getInstance(int thread_id) {if (instance == NULL) {//双检锁 1lock1->Lock();if (instance == NULL) {//2instance = new Singleton(thread_id);}lock1->unLock();}return instance;
}
mutex mtLock;
void print_thread_id(int thread_id) {Singleton* instance = Singleton::getInstance(thread_id);mtLock.lock();cout << thread_id<<" ";cout << instance->get_value()<<endl;mtLock.unlock();
}
int main() {thread thread1[3];for (int i = 0; i < 3; i++) {thread1[i] = thread(print_thread_id, i);}for (auto& th : thread1) th.join();//等线程结束return 0;
}

Scott Meyer/简洁的singleton写法

Singleton *Singleton::getinstance(){    static Singleton p;    return &p; }               

解耦

程序设计经常提到的解耦,到底是要解除什么之间的耦合

解耦,就是将程序积木化

就像我们玩的积木一样,各个积木可以组合在一起而形成一个形状,又可以拆分,又可以替换,因

为基本上各个积木块都是独立的,只要他们之间的接口(形状)匹配,就可以灵活地组合在一起。

当然,这是理想状态。解耦是在逐达到这个理想状态。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/664695.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

计算机网络自顶向下Wireshark labs-HTTP

我直接翻译并在题目下面直接下我的答案了。 1.基本HTTP GET/response交互 我们开始探索HTTP&#xff0c;方法是下载一个非常简单的HTML文件 非常短&#xff0c;并且不包含嵌入的对象。执行以下操作&#xff1a; 启动您的浏览器。启动Wireshark数据包嗅探器&#xff0c;如Wir…

【数据结构】 - 队列 栈

theme: smartblue 一、队列 1、概念 队列&#xff08;Queue&#xff09;是一种常见的数据结构&#xff0c;它按照先进先出&#xff08;First In First Out&#xff0c;FIFO&#xff09;的原则进行元素操作。在队列中&#xff0c;新元素总是被添加到队列的末尾&#xff0c;而…

spring-boot-actuator 服务监控

1 概述 服务启动时&#xff0c;通过spring-boot-actuator 监控es等服务是否连接成功等 2 依赖 <!-- 服务监控 --> <dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-actuator</artifactId><…

2024年美国大学生数学建模竞赛(E题)财产保险建模|MCDA/随机森林建模解析,小鹿学长带队指引全代码文章与思路

我是鹿鹿学长&#xff0c;就读于上海交通大学&#xff0c;截至目前已经帮500人完成了建模与思路的构建的处理了&#xff5e; 本文运用利用时间序列和强化学习结合DQN算法&#xff0c;解决保险业可持续性问题&#xff1b;采用MCDA和随机森林&#xff0c;应对地产业保险挑战&…

[Unity Sentis] Unity Sentis 详细步骤工作流程

文章目录 1. 导入模型文件支持的模型创建运行时模型导入错误 2. 为模型创建输入将数组转换为张量创建多个输入进行操作 3. 创建一个引擎来运行模型创建一个Worker后端类型 4. 运行模型5. 获取模型的输出获取张量输出多个输出打印输出 1. 导入模型文件 要导入 ONNX 模型文件&am…

电脑怎么录屏?打造专业级视频内容!

随着科技的进步&#xff0c;电脑已经深入到我们的日常生活和工作中。而在这个数字时代&#xff0c;录制屏幕内容变得日益重要。无论是制作教程、分享游戏技巧&#xff0c;还是记录重要的演示&#xff0c;录屏都是一个不可或缺的功能。可是电脑怎么录屏呢&#xff1f;本文将深入…

Cmake语法学习3:语法

1.双引号 1.1 命令参数 1&#xff09;介绍 命令中多个参数之间使用空格进行分隔&#xff0c;而 cmake 会将双引号引起来的内容作为一个整体&#xff0c;当它当成一个参数&#xff0c;假如你的参数中有空格&#xff08;空格是参数的一部分&#xff09;&#xff0c;那么就可以使…

web前端较新的前端技术和趋势

Web前端的技术发展迅速&#xff0c;不断有新工具和框架出现。以下是一些较新的前端技术和趋势&#xff1a; 框架和库&#xff1a; React&#xff1a;Facebook开发的一个用于构建用户界面的JavaScript库&#xff0c;目前非常流行。 Vue.js&#xff1a;一个渐进式JavaScript框架…

java DateTimeFormatter 使用

DateTimeFormatterBuilder dateTimeFormatterBuilder new DateTimeFormatterBuilder().append(DateTimeFormatter .ofPattern(“[MM/dd/yyyy]” “[dd-MM-yyyy]” “[yyyy-MM-dd]” “[yyyy-M-d]” “[dd MMM yyyy]”)); LocalDate dd LocalDate.parse(“03 Feb 2024”,…

国自然结题报告为什么不能下载?如何解决

一直以来&#xff0c;国自然的结题报告都不是只能在线查看&#xff0c;这给很多科研人员带来一些不方便&#xff0c;因为在线查看的话&#xff0c;受限于网络&#xff0c;必须有网的时候才能看&#xff0c;并且官方网站的响应比较慢&#xff0c;想快速翻页&#xff0c;基本不可…

《苍穹外卖》知识梳理P2-公共类说明

《苍穹外卖》知识梳理P2 上一节中&#xff0c;进行了项目结构的搭建知识梳理P1 公共类说明 所有公共类都位于common模块下&#xff0c;由于每个包下的类很多&#xff0c;在此不一一列举&#xff1b; 1.constant包&#xff1a;主要存放常量类&#xff0c;类中只有静态常量&am…

leetcode-链表专题

25.K个一组翻转链表 题目链接 25. K 个一组翻转链表 - 力扣&#xff08;LeetCode&#xff09; 解题思路 # Definition for singly-linked list. # class ListNode: # def __init__(self, val0, nextNone): # self.val val # self.next next class So…

【Java程序设计】【C00240】基于Springboot的班级综合测评管理系统(有论文)

基于Springboot的班级综合测评管理系统&#xff08;有论文&#xff09; 项目简介项目获取开发环境项目技术运行截图 项目简介 这是一个基于Springboot的班级综合测评管理系统 本系统分为学生功能模块、管理员功能模块以及教师功能模块。 管理员功能模块&#xff1a;管理员功能…

MySQL中DML操作(九)

MySQL中DML操作&#xff08;九&#xff09; DML语言是操作数据的语言 一、添加数据(INSERT) 1.1 选择插入 INSERT INTO 表名(列名 1 &#xff0c;列名 2 &#xff0c;列名 3.....) VALUES(值 1 &#xff0c;值 2 &#xff0c;值 3......); 1.2 完成插入 INSERT INTO 表名…

L1-020 帅到没朋友分数 20

当芸芸众生忙着在朋友圈中发照片的时候&#xff0c;总有一些人因为太帅而没有朋友。本题就要求你找出那些帅到没有朋友的人。 输入格式&#xff1a; 输入第一行给出一个正整数N&#xff08;≤100&#xff09;&#xff0c;是已知朋友圈的个数&#xff1b;随后N行&#xff0c;每…

MySQL学习记录——일 MySQL 安装、配置

文章目录 1、卸载内置环境2、安装MySQL3、启动4、登录5、配置my.cnf 当前环境是1核2G云服务器&#xff0c;CentOS7.6。要在root用户下进行操作 1、卸载内置环境 云服务器中有可能会自带mysql还有mariadb这样的数据库服务&#xff0c;在安装我们mysql前&#xff0c;得先查找一下…

迪文串口屏数据的隐藏功能

一、概述 由于项目中在使用迪文屏显示数据的时候&#xff0c;需要在数据为0的时候不显示0&#xff0c;而迪文屏默认的数据变量在无值的时候显示为0&#xff0c;此时可以使用数据的隐藏功能指令 二、具体实现方法 1、可以使用描述指针地址来实现数据的隐藏&#xff0c;查看应用…

大模型重塑车载语音交互:赛道巨头如何引领新周期?

车载语音交互赛道正进入新一轮竞争周期。 高工智能汽车注意到&#xff0c;传统车载语音交互赛道当前基本已进入成熟期&#xff0c;主要为任务型助手&#xff0c;包括从单轮对话到多轮对话&#xff0c;单音区到多音区&#xff0c;从单一的导航、多媒体娱乐等座舱功能扩展智能驾…

Ubuntu 添加字体

Ubuntu 添加字体 Ubuntu如何添加新的字体&#xff1f;似乎远远没有Windows方便呀&#xff0c;查询了一些资料&#xff0c;与大家分享。 方法1 根据字体名称直接安装 oyroy-FMVU08001:~$ sudo apt-get install fonts-wqy-zenhei [sudo] roy 的密码&#xff1a; 正在读取软件…

装饰property的两种使用方法

property的作用 在调用对象方法时可以向调用对象属性一样 如何使用property 定义时&#xff0c;在实例方法的基础上添加 property 装饰器&#xff1b;并且仅有一个self参数调用时&#xff0c;无需括号 方法&#xff1a;foo_obj.func()property属性&#xff1a;foo_obj.prop…