C++拷贝构造函数和运算符重载

目录

一,拷贝构造函数

二,运算符重载


一,拷贝构造函数

概念:在类的定义中,构造函数只是单纯将内置类型进行初始化,而拷贝构造函数是将整个类进行拷贝到另一个类中进行初始化。在定义拷贝构造函数时,只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰),在用已存在的类类型对象创建新对象时由编译器自动调用。

这里要说明的是,拷贝构造函数也是特殊的成员函数,其特征如下:

        1,拷贝构造函数是构造函数的一个重载形式。

        2,拷贝构造函数的参数只有一个且必须是类类型对象的引用,使用传值方式编译器直接报错,因为形参相当于实参的临时拷贝,在拷贝中会触发另一个拷贝构造函数的调用,从而引发无穷递归调用。传引用的话只会将源对象的别名进行拷贝,不会进行整体拷贝,从而避免了这种问题。

class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}//Date(const Date d)// 错误写法: 编译报错,会引发无穷递归Date(const Date& d)// 正确写法{_year = d._year;_month = d._month;_day = d._day;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;//调用构造函数Date d2(d1);//用类进行初始化,会调用拷贝构造函数return 0;
}

引发无穷递归的过程图

        3,若未显式定义,编译器会生成默认的拷贝构造函数。默认的拷贝构造函数只会进行浅拷贝,或者值拷贝。(浅拷贝是指只拷贝对象的基本数据类型和引用地址,不会拷贝引用地址所指向的对象,即原对象中的数据所指向的空间将与拷贝后的对象的数据所指向的空间相同)。

        这里要说明的是默认拷贝构造函数的处理对象不跟默认构造函数一样,但处理自定义类型的方式跟默认构造函数一样。默认拷贝构造函数会对自定义类型和内置类型都做处理,其中,内置类型成员完成浅拷贝,自定义类型成员将会调用这个成员的拷贝构造

#include <iostream>
using namespace std;
class Time
{
public:Time() {_hour = 1;_minute = 1;_second = 1;}Time(const Time& t) {_hour = t._hour;_minute = t._minute;_second = t._second;cout << "Time对象的拷贝构造" << endl;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 内置类型int _year = 1970;int _month = 1;int _day = 1;// 自定义类型,将会调用此对象的拷贝构造Time _t;
};
int main()
{Date d1;Date d2(d1);return 0;
}

d1类运行的调试内部分析图(构造函数)

d2类运行的调试内部分析图(拷贝构造函数)

        下面问题来了,既然有默认拷贝构造函数,我们需要不需要再定义拷贝构造函数呢?

        请注意上面说的,默认拷贝构造只会进行浅拷贝,当我们在对象成员中开辟了动态空间时,使用默认拷贝构造将会出现问题。请看以下代码:

//此代码运行时将会崩溃
#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (!_array) {perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}~Stack(){if (_array) {free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;Stack s2(s1);return 0;
}

分析: 

        首先,s1先调用构造函数创建,在构造函数中开辟了10个元素的空间,然后,s2对象使用s1拷贝构造,Stack对象中没有自己定义,系统将生成一份默认的拷贝构造函数进行浅拷贝,这时,s1和s2栈结构将同时指向一块内存空间,当函数退出时,s2和s1会自动调用析构函数进行销毁,这就会造成s1和s2指向的同一块空间销毁两次,系统崩毁。

        解决上面的问题不难,我们只需将s1和s2分别指向不同的空间即可,这时,需要我们自己定义拷贝构造函数。

#include <iostream>
using namespace std;
typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (!_array) {perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}//定义拷贝构造Stack(const Stack& S){_size = S._size;_capacity = S._capacity;_array = (DataType*)malloc(_capacity * sizeof(DataType));if (!_array) {perror("malloc申请空间失败");return;}}~Stack(){if (_array) {free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;Stack s2(s1);return 0;
}

总:如果类中没有涉及空间资源的申请时,拷贝构造函数可以不写,但是一旦涉及到资源空间的申请时,则拷贝构造函数是一定要写上的。

拷贝构造函数典型调用场景:

        1,使用已存在对象创建新对象。如同以上Stack类中s2的创建。

/*..........*/

int main()
{
    Stack s1;
    Stack s2(s1);//使用已存在的对象s1来创建对象s2
    return 0;
}

        2,函数参数类型为类类型对象——解析:因为在传参过程中,形参就相当于实参的临时拷贝,相当于用实参来创建形参。

        3,函数返回值类型为类类型对象——解析:当函数返回时,函数内局部对象的生命周期结束,但是临时变量的值会被拷贝到调用函数的栈帧中,或者通过引用传递给调用函数。当返回类类型对象时,直接将此类对象拷贝到调用函数栈帧中。

总结一句话,只要是运用了类对象与类对象直接赋值进行初始化的情况,系统就会调用拷贝构造。而为了提高效率,一般能用引用就用引用。


二,运算符重载

引入:

        在内置类型中,系统给我们自动定义了很大运算符,如:+、-、*、/、++、--、>等。这些内置类型和内置类型运算符的使用都是系统定义好的,可直接使用。现在问题来了,自定义类型要想使用这些运算符又当如何?由于自定义类型是我们自己定义的,系统不知道其结构,所以无法直接供我们使用,但C++为了增强代码的可读性引入了运算符重载的概念,可让自定义类型也使用这些运算符。运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

运算符重载的定义:

        函数名:关键字operator后面接需要重载的运算符符号。

        函数原型:返回值类型 operator操作符(参数列表)。

这里有以下5个注意:

        1,不能通过连接其他符号来创建新的操作符:比如operator@

        2,重载操作符必须有一个类类型参数

        3,用于内置类型的运算符,其含义不能改变,例如:内置的整型+,不能改变其+本身的含义

        4,作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this指针。

        5,“ .*    ::    sizeof    ?:    . ” 注意这5个运算符不能重载。这个经常在笔试选择题中出现。

在类的内部定义

第一个参数为this指针

// '='操作符,返回类型为类类型Date的引用
Date& operator=(const Date& d);

Date& Date::operator=(const Date& d);//此种情况是在类中声明,在外部实现,用限定符说明是类对象中的成员函数
// "+="操作符,返回类型为类类型Date的引用
Date& operator+=(int day);

Date& Date::operator+=(int day);//此种情况是在类中声明,在外部实现,用限定符说明是类对象中的成员函数
// '+'操作符,返回类型为类类型Date的引用
Date operator+(int day);

Date Date::operator+(int day);//此种情况是在类中声明,在外部实现,用限定符说明是类对象中的成员函数
// '-'操作符,返回类型为类类型Date的引用
Date operator-(int day);

Date Date::operator-(int day);//此种情况是在类中声明,在外部实现,用限定符说明是类对象中的成员函数
// "-="操作符,返回类型为类类型Date的引用
Date& operator-=(int day);

Date& Date::operator-=(int day);//此种情况是在类中声明,在外部实现,用限定符说明是类对象中的成员函数

在类的外部定义

没有默认参数,需要把类写入形参中

// "+="操作符,返回类型为类类型Date的引用
Date& operator+=(const Date& d1, int day);
// '+'操作符,返回类型为类类型Date的引用
Date operator+(const Date& d1, int day);
// '-'操作符,返回类型为类类型Date的引用
Date operator-(const Date& d1, int day);
// "-="操作符,返回类型为类类型Date的引用
Date& operator-=(const Date& d1, int day);

运用细节演示:

        (1)首先,我们先对“ >,<,>=,<=,==,!= ”这几个简单运算符在类对象内定义进行演示和解说。

#include <iostream>
#include <assert.h>
using namespace std;
//再次提醒一下,类对象中的成员函数第一个默认参数为this,且指向此对象,下面运用时就不做说明
class Date
{
public:// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// ==运算符重载bool operator==(const Date& d){return _year == d._year &&_month == d._month &&_day == d._day;}// !=运算符重载bool operator != (const Date& d){return !(*this == d);}// >运算符重载bool operator>(const Date& d){if (_year > d._year)return true;else if (_year == d._year && _month > d._month)return true;else if (_year == d._year && _month == d._month && _day > d._day)return true;return false;}// >=运算符重载bool operator >= (const Date& d){return *this > d || *this == d;}// <运算符重载bool operator < (const Date& d){if (_year < d._year)return true;else if (_year == d._year && _month < d._month)return true;else if (_year == d._year && _month == d._month && _day < d._day)return true;return false;}// <=运算符重载bool operator <= (const Date& d){return *this < d || *this == d;}
private:int _year;int _month;int _day;
};
int main()
{Date d1;Date d2(2023, 10, 16);cout << (d1 > d2) << endl;//d1 > d2等效于d1.operator>(d2),这两个表达的意思一样cout << d1.operator>(d2) << endl;return 0;
}

        在上面代码中要说明的是在运算符表示过程中,我们即可用函数来表示运算符的含义,也可直使用运算符的形式表示含义。

在上面的运算符函数中:

d1 > d2等效于d1.operator(d2)

d1 == d2等效于d1.operator(d2)

上面的输出流中之所以(d1 > d2)用括号括起来是因为“ << ”操作符的运算级别高,会先与之运算。

        (2)接下来我们对“ +,-,+=,-= ”这几个简单运算符在类对象内定义进行演示和解说。

#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:// 获取某年某月的天数int GetMonthDay(int year, int month){assert(year >= 1 && (month >= 1 && month <= 12));int MonthDays[] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };if ((month == 2) && ((year % 400 == 0) || (year % 4 == 0 && year % 100 != 0)))return 29;return MonthDays[month];}// 全缺省的构造函数Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 日期-天数Date operator-(int day){_day -= day;while (_day < 0) {if (_month == 1) {_year--;if (_year < 0) {perror("Date Count Error:");exit(-1);}_month = 12;}else {_month--;}_day += GetMonthDay(this->_year, this->_month);}return *this;}// 日期-=天数Date& operator-=(int day){Date DateCopy(*this);//用临时对象返回,相当于函数的返回,需要有个对象去接收,而改变的只是临时对象//像直接使用类d1 -= 5,表达的只是此函数的功能,d1的值不会改变,若是d1 = d1 -= 5将会改变//DateCopy = DateCopy.operator-(day);//return DateCopy;//this指针直接指向的是此对象(即此类),用this指针来改变相当于直接改变了d1,类似于达到了改变d1的效果*this = DateCopy.operator-(day);return *this;}// 日期+=天数Date& operator+=(int day){_day += day;while (_day > GetMonthDay(this->_year, this->_month)) {_day -= GetMonthDay(this->_year, this->_month);_month++;if (_month > 12) {_year++;_month -= 12;}}return *this;}// 日期+天数Date operator+(int day){Date DateCopy(*this);DateCopy.operator+=(day);return DateCopy;}// 日期-日期 返回天数int operator-(const Date& d){int YearDay = 0, MonthDay = 0, Day = _day - d._day;while (--_month) {MonthDay += GetMonthDay(_year, _month);}int month = d._month;while (--month) {MonthDay -= GetMonthDay(d._year, month);}_year--;int year = d._year - 1;for (int YearCount = _year - year; YearCount > 0; YearCount--) {YearDay += 365;if ((_year % 400 == 0) || ((_year % 4 == 0) && (_year % 100 != 0)))YearDay += 1;_year--;}return YearDay + MonthDay + Day;}void Print() {cout << _year << "/" << _month << "/" << _day << endl;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023, 10, 16);Date d2;//+=和+运算符重载//这里要说明的是,由于"+="没有返回值,所以在函数对象内部改变的时候一定要用this指针来改变,this指针指向d1d1 += 365;cout << "d1重载d1+=365: ";d1.Print();d2 = d1 + 23;cout << "d2重载d2=d1+23: ";d2.Print();//-=和-的运算符重载//"-="运算符与"+="同理d1 -= 365;cout << "d1重载d1-=365: ";d1.Print();d2 = d1 - 365;cout << "d2重载d2=d1-365: ";d2.Print();//日期-日期的运用cout << "d2-d1: " << d2 - d1 << endl;return 0;
}

        这里要注意的是函数的返回类型,“ +=,-= ”运算一般是不需要返回类型的,所以在内部实现要用this访问,从而间接实现原对象的运算。“ +,- ”操作符要有返回类型去接收,可直接返回临时对象。

        (3)“ 前置++,后置++,前置--,后置-- ”在类对象内定义进行演示和解说。

    // 后置++,形参为int,C++专门规定,为了与前置++区分
    Date operator++(int) {
        Date ret(*this);
        *this += 1;
        return ret;
    }
    // 前置++
    Date& operator++() {
        *this += 1;
        return *this;
    }
    // 后置--,形参为int,C++专门规定,为了与前置--区分
    Date operator--(int) {
      //先将原本类进行拷贝,然后将此对象里的值--,返回此对象,实现了先赋值,再--
        Date ret(*this);
        *this -= 1;
        return ret;
    }
    // 前置--
    Date& operator--() {
      //直接返回*this,实现了先把对象的值--,然后将此对象返回,实现了先--,再赋值
        *this -= 1;
        return *this;
    }

        这里要注意的是前置++、前置--和后置++、后置--的写法和前置和后置的实现。如上代码中C++的专门规定前置与后置的写法,前置的实现是先进行运算,然后再进行赋值,所以要直接用this进行。后置的实现要先进行赋值,然后再运算。这里我们虽然实现了this的运算,但返回的是运算前的对象,进而实现了此原理。

        (4)赋值运算符。赋值运算符比较特殊,我们要区分它与拷贝构造。当类与类进行赋值时,若类还没有进行初始化,将会调用拷贝构造;若已进行了初始化,将会调用赋值运算符。

        1,赋值运算符一般也要设置返回类型,一般返回 *this,因为我们要保证连续赋值的情况。

#include <iostream>
#include <assert.h>
using namespace std;
class Date
{
public:Date(int year = 1900, int month = 1, int day = 1){_year = year;_month = month;_day = day;}// 赋值运算符重载Date& operator=(const Date& d){_year = d._year;_month = d._month;_day = d._day;return *this;}
private:int _year;int _month;int _day;
};
int main()
{Date d1(2023, 11, 9);Date d2, d3, d4;d4 = d3 = d2 = d1;//调用赋值运算符重载return 0;
}

        2,赋值运算符只能重载成类的成员函数不能重载成全局函数。

class Date
{
public:
    Date(int year = 1900, int month = 1, int day = 1)
    {
        _year = year;
        _month = month;
        _day = day;
    }
    int _year;
    int _month;
    int _day;
};
// 赋值运算符重载成全局函数,注意重载成全局函数时没有this指针了,需要给两个参数
Date& operator=(Date& left, const Date& right)//出现错误,出现在全局中
{
    left._year = right._year;
    left._month = right._month;
    left._day = right._day;
    return left;
}

        原因:赋值运算符如果不显式实现,编译器会生成一个默认的。此时用户再在类外自己实现 一个全局的赋值运算符重载,就和编译器在类中生成的默认赋值运算符重载冲突了,因此,赋值运算符重载只能是类的成员函数。

        3,用户自己如果没有定义赋值运算符,编译器会生成一个默认赋值运算符重载,以浅拷贝的形式实现拷贝。内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符重载完成赋值。

#include <iostream>
using namespace std;
class Time
{
public:Time(){_hour = 1;_minute = 1;_second = 1;}Time& operator=(const Time& t){		_hour = t._hour;_minute = t._minute;_second = t._second;return *this;}
private:int _hour;int _minute;int _second;
};
class Date
{
private:// 基本类型(内置类型)int _year = 1970;int _month = 1;int _day = 1;// 自定义类型Time _t;
};
int main()
{Date d1;Date d2;d1 = d2;return 0;
}

d1 = d2运行的调试解图

        默认赋值运算符重载的原理跟默认拷贝构造一样,如果类中未涉及到资源空间的管理,赋值运算符是否实现都可以;一旦涉及到资源管理则必须要自己定义实现。

        (5)流操作符的重载。在C++程序中,我们也可实现插入操作符(<<)和提取操作符(>>)的重载。

        我们首先要认识ostream和istream。ostream用于处理输出流的类,类中定义了插入操作符(<<)来将数据写入输出流;istream是用于处理输入流的类。它定义了提取操作符(>>)来从输入流中提取数据。

定义样例:

在类的内部定义

第一个参数默认this指针

class Date
{
private:
    int _year = 2023;
    int _month = 10;
    int _day = 16;
};

//没有返回类型的情况

void operator<<(ostream& out);

void operator>>(istream& in);

//在类内部声明,在外部进行定义,也相当于在内部定义

void Date::operator<<(ostream& out);

void Date::operator>>(istream& in);

//返回类型为ostream和istream,这种情况为了支持连续输入输出的情况,因为连续输入输出需要有返回值去接收

ostream& operator<<(ostream& out);

istream& operator>>(istream& in);

在类的外部定义

没有默认参数,需要把类写入形参中

class Date
{
private:
    int _year = 2023;
    int _month = 10;
    int _day = 16;
};

//没有返回类型的情况

void operator<<(ostream& out, const Date& d1);

void operator>>(istream& out, const Date& d1);

//返回类型为ostream和istream,这种情况为了支持连续输入输出的情况,因为连续输入输出需要有返回值去接收

ostream& operator<<(ostream& out, const Date& d1);

istream& operator>>(istream& out, const Date& d1);

首先我们先观察以下代码:

#include<iostream>
using namespace std;
class Date
{
public:void operator<<(ostream& out) {out << _year << "/" << _month << "/" << _day << endl;}void operator>>(istream& in){in >> _year >> _month >> _day;}
private:int _year = 2023;int _month = 10;int _day = 16;
};
int main()
{Date d1;//cin >> d1;// 相当于cin.operator>>(d1);系统报错d1 >> cin;// 相当于d1.operator<<(cin);正常运行//cout << d1;// 相当于cout.operator<<(d1);系统报错d1 << cout;// 相当于d1.operator<<(cout);正常运行return 0;
}

        因为双操作数的运算符第一个参数是左操作数,第二个参数是右操作数,而在Date对象内部中,Date类对象默认占第一个位置,导致了以上情况的发生。

        要想解决以上的问题,保证代码的可读性,就改变参数的位置,显然这不能在类对象的内部定义,必须在外部定义。

无返回类型的使用(即不支持连续使用)

#include<iostream>
using namespace std;
class Date
{
public:int _year = 2023;int _month = 10;int _day = 16;
};
void operator<<(ostream& out, const Date& d)
{out << d._year << "/" << d._month << "/" << d._day << endl;
}
void operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;
}
int main()
{Date d1;cin >> d1;// 相当于cin.operator>>(d1);正常运行cout << d1;// 相当于cout.operator<<(d1);正常运行return 0;
}

运用图解

有返回类型的使用(即支持连续使用)

#include<iostream>
using namespace std;
class Date
{
public:int _year = 2023;int _month = 10;int _day = 16;
};
ostream& operator<<(ostream& out, const Date& d)
{out << d._year << "/" << d._month << "/" << d._day << endl;return out;
}
istream& operator>>(istream& in, Date& d)
{in >> d._year >> d._month >> d._day;return in;
}
int main()
{Date d1, d2;cin >> d1 >> d2;// 从左到右连续进行,返回类型为输入流类的引用cout << d1 << endl << d2;// 从左到右连续进行,返回类型为输出流类的引用return 0;
}

这里要注意几个问题:

        1,当在外部定义时,因为是在外面访问成员对象的,因此权限必须设为public。

        2,在使用 “ >> ” 运算符时,因为是要对成员对象进行输入流给值的操作,所以类对象不能加用const。

        3,连续使用运算符操作相当于连续不断的赋值操作,这里需注意函数的返回类型。

总结一下:除流操作符以外的操作符一般是实现成员函数,“ >>,<< ”流运算要在外部实现,只有这样才能让流对象做第一个参数,实现可读性。还有就是要注意函数的返回类型,因为有了返回类型,才能实现连续使用运算符的操作。

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

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

相关文章

Idea 编译SpringBoot项目Kotlin报错/Idea重新编译

原因应该是一次性修改了大量的文件, SpringBoot项目启动Kotlin报错, Build Project也是同样的结果, 报错如下 Error:Kotlin: Module was compiled with an incompatible version of Kotlin. The binary version of its metadata is 1.9.0, expected version is 1.1.13. Build-&…

C#中.NET 6.0 Windows窗体应用通过EF访问数据库并对数据库追加、删除记录

目录 一、应用程序设计 二、应用程序源码 三、生成效果 前文作者发布了在.NET 6.0 控制台应用中通过EF访问已有数据库&#xff0c;事实上&#xff0c;在.NET 6.0 Windows窗体应用中通过EF访问已有数据库也是一样的。操作方法基本一样&#xff0c;数据库EF模型和上下文都是自…

istio学习笔记-安装

Istioldie 1.18 / 安装指南 基于Kubernetes的Istio的微服务架构需要安装以下组件&#xff1a; Istio控制平面组件&#xff1a;包括Istio-Pilot、Istio-Policy、Istio-Telemetry等。这些组件负责微服务的管理和配置&#xff0c;如流量管理、策略执行、遥测数据收集等。数据平面…

一文讲清生产质量场景的数据分析思路及案例实战

今天&#xff0c;顺着制造业数据分析这个大主题&#xff0c;我们来讲讲质量管理数据分析。   说起质量管理&#xff0c;就是对所生产的产品质量进行管理&#xff0c;其最终目的就是保证客户收到的产品质量&#xff0c;提高客户满意度&#xff0c;减少退货和维修的数量。质量管…

Javaweb之javascript事件案例的详细解析

1.6.4 案例 1.6.4.1 需求说明 接下来我们通过案例来加强所学js知识点的掌握。 需求如下3个&#xff1a; 点击 “点亮”按钮 点亮灯泡&#xff0c;点击“熄灭”按钮 熄灭灯泡 输入框鼠标聚焦后&#xff0c;展示小写&#xff1b;鼠标离焦后&#xff0c;展示大写。 点击 “全…

Elasticsearch:检索增强生成 (Retrieval Augmented Generation -RAG)

作者&#xff1a;JOE MCELROY 什么是检索增强生成 (RAG) 以及该技术如何通过提供相关源知识作为上下文来帮助提高 LLMs 生成的响应的质量。 生成式人工智能最近取得了巨大的成功和令人兴奋的成果&#xff0c;其模型可以生成流畅的文本、逼真的图像&#xff0c;甚至视频。 就语…

ubuntu小技巧30--23.10桌面版安装钉钉启动报错undefined symbol: FT_Get_Color_Glyph_Layer

ubuntu小技巧30-- 23.10桌面版安装钉钉启动报错undefined symbol: FT_Get_Color_Glyph_Layer 介绍解決方法说明 介绍 近期在电脑上安装了 ubuntu 23.10桌面版本, 安装最新版钉钉后无法正常打开软件&#xff0c;报错 undefined symbol: FT_Get_Color_Glyph_Layer &#xff0c;具…

​软考-高级-系统架构设计师教程(清华第2版)【第5章 软件工程基础知识(190~233)-思维导图】​

软考-高级-系统架构设计师教程&#xff08;清华第2版&#xff09;【第5章 软件工程基础知识&#xff08;190~233&#xff09;-思维导图】 课本里章节里所有蓝色字体的思维导图

FreeRTOS源码阅读笔记3--queue.c

消息队列可以应用于发送不定长消息的场合&#xff0c;包括任务与任务间的消息交换&#xff0c;队列是 FreeRTOS 主要的任务间通讯方式&#xff0c;可以在任务与任务间、中断和任务间传送信息&#xff0c;发送到 队列的消息是通过拷贝方式实现的&#xff0c;这意味着队列存储…

降低城市内涝风险,万宾科技内涝积水监测仪的作用

频繁的内涝会削弱和损坏城市的关键基础设施&#xff0c;包括道路、桥梁和公用设施。城市内涝风险降低可以减少交通中断事件&#xff0c;也可以保护居民安全并降低路面维修等成本&#xff0c;进一步确保城市基本服务继续发挥作用。对城市可持续发展来讲有效减少内涝的风险是重要…

如何正确使用 JavaScript 中的 slice() 方法

在 JavaScript 中&#xff0c;slice() 是一个常用的数组方法&#xff0c;用于从现有数组中提取一部分元素&#xff0c;然后返回一个新的数组。它是一个非常有用的工具&#xff0c;可以帮助你在不改变原始数组的情况下操作数组的子集。本文将介绍 slice() 的基本概念、使用方法、…

Unity中Shader的矩阵加减法

文章目录 前言一、什么是矩阵矩阵就是一组数的阵列 二、矩阵的加法三、矩阵的负值四、矩阵的减法五、矩阵的表示 前言 Unity中Shader用到的矩阵加减法&#xff0c;以及矩阵的一些基础常识 一、什么是矩阵 矩阵就是一组数的阵列 1 2 3 4 5 6 二、矩阵的加法 两个矩阵相加就是…