当前位置: 首页 > news >正文

【C++】类和对象(二)

目录

一、默认成员函数

二、构造函数

1、构造函数概念

2、构造函数编写

3、默认构造函数

4、内置类型成员的补丁

三、析构函数

1、析构函数概念 

2、析构函数编写

3、默认析构函数 

四、拷贝构造函数

1、拷贝构造函数概念及编写

2、默认拷贝构造函数

3、拷贝构造函数调用场景

五、赋值运算符重载

1、运算符重载概念

2、运算符重载编写

3、赋值运算符重载

3.1、赋值运算符重载格式

3.2、赋值运算符重载位置

4、默认赋值运算符重载

六、const成员

七、取地址及const取地址操作符重载


一、默认成员函数

 当我们写了一个没有成员的空类时:

class Date {};

 这个空类里面是真的什么都没有吗?其实不是的,任何类在什么都不写时,编译器会自动生成以下 6 个默认成员函数:

  1. 构造函数
  2. 析构函数
  3. 拷贝构造函数
  4. 赋值运算符重载
  5. 取地址操作符重载
  6. const修饰的取地址操作符重载

二、构造函数

1、构造函数概念

在用类实例化出对象之后,我们通常需要对这个对象进行初始化,以我们学过的栈为例:

typedef int DataType;
class Stack
{
public:void Init(){_array = (DataType*)malloc(sizeof(DataType) * 3);if (NULL == _array){perror("malloc申请空间失败!!!");return;}_capacity = 3;_size = 0;}void Push(DataType data){CheckCapacity();_array[_size] = data;_size++;}//....//....
private:void CheckCapacity(){...;}
private:DataType* _array;int _capacity;int _size;
};
int main()
{Stack s;s.Init(); //每次实例化出对象都要进行初始化s.Push(1);s.Push(2);s.Push(3);return 0;
}

但如果每次创建对象时都调用 s.Init();设置初始化,未免有点麻烦。那能否在对象被创建时,就直接让对象自动初始化呢?

为了实现这个功能,我们引入了一个概念:构造函数。构造函数是一个特殊的成员函数,名字与类名相同,创建类类型对象时由编译器自动调用,以保证每个数据成员都有一个合适的初始值,并且在对象整个生命周期内只调用一次

2、构造函数编写

 构造函数是特殊的成员函数。需要注意的是,构造函数虽然名称叫构造,但是构造函数的主要任
务并不是开空间创建对象,而是初始化对象

构造函数的特性:

  1. 函数名与类名相同
  2. 无返回值
  3. 对象实例化时编译器自动调用对应的构造函数
  4. 构造函数可以重载

 根据构造函数特性,我们提炼出核心内容:构造函数的函数名已经被确定好了,就是类名。构造函数没有返回值,而且不需要在函数名前加 void 。我们可以设置多个构造函数,以完成不同需求的初始化方案。构造函数在对象实例化时自动调用。

 现在我们使用构造函数来编写栈的初始化:

 这里写了两个构造函数,分为 有参构造 Stack(int n);和 无参构造 Stack();

 我们在实例化对象的时候直接写 Stack s;默认调用的构造函数是无参构造函数 Stack();,即不给栈开辟空间。

 如果我们想在初始化时给栈开辟指定的空间,可以在实例化对象的时候写 Stack s(n);调用有参构造函数 Stack(int n);,例如:

 注意:如果通过无参构造函数创建对象时,对象后面不用跟括号,否则就成了函数声明。例如:

Stack s();

编译器无法区分这段代码是一个函数的声明,还是调用构造函数。


在以后编写构造函数时,我们也可以利用缺省参数来把有参构造函数与无参构造函数合并来写:


3、默认构造函数

 如果我们在类中没有写构造函数,那么编译器会自动生成一个无参的默认构造函数。如果我们写了构造函数,那么编译器不会自动生成。

现在把我们自己写的构造函数注释掉:

 程序可以正常执行,因为编译器生成了默认的构造函数。

我们自己写一个不可用的构造函数:

程序运行失败,因为我们已经写了一个构造函数了,所以编译器不会自己生成,尽管我们写的构造函数不可用。


那么既然编译器会自动生成默认构造函数,还需要我们自己来编写吗?

关于这个问题,我们上手实践一下就可以了,先把构造函数注释掉,然后运行程序观察结果:

 发现程序运行结果全是随机数,没有价值。

 那么编译器生成这个默认构造函数是为了什么?好像完全没用。实际上这里出现的问题,是C++祖师爷在设计语言之初时没有解决掉的问题。

在C++语言中,我们把类型分为两种,一种为内置类型,一种为自定义类型。内置类型就是语言提供的数据类型,如 int、char、double 等等。自定义类型则为我们使用 class、struct、union 等关键字自己定义的类型。  

编译器生成的默认构造函数对于自定义类型的成员,会调用他的构造函数。而对于内置类型的成员不做处理

//自定义类型会生成默认构造
class Date
{
//...
//...
private://对内置类型不做处理int _year;int _month;int _day;
};

 所以以后我们遇到在对象初始化时需要对内置类型成员设置初始值时,就不要再使用默认构造函数了,自己编写才是王道。


 默认构造函数在什么样的情况下会有作用呢? 经过上面的学习,同学们心中应该有了答案,那就是在对象初始化时,如果不需要对内置类型成员设置初始值,默认构造函数就发挥了很大的用处。

还是以栈来作为例子进行说明:

 因为对象 q1 中的成员类型是自定义类型 Stack 的,所以编译器自动生成的默认构造函数会调用对象 q1 中的两个自定义类型成员的构造函数。

4、内置类型成员的补丁

 前面说过,编译器生成的默认构造函数不会对内置类型成员做处理,是当时C++祖师爷在设计语言时没有解决掉的问题。为了弥补这个错误,C++11 中针对内置类型成员不初始化的缺陷,又打了补丁,即:内置类型成员变量在类中声明时可以给默认值

 在内置类型成员的声明位置给上了缺省值。在对象实例化后,如果我们没有写构造函数,那么这些成员就会被初始化为缺省值。如果我们自己写了构造函数,这些成员就会被初始化为我们指定的初始值。

有了这个补丁,C++的默认构造函数才算是完备了,自定义类型与内置类型混合使用起来才更加方便。

补充内容:无参的构造函数和全缺省的构造函数都称为默认构造函数,并且默认构造函数只能有一个。
注意:无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认构造函数。即不传参就可以调用的构造函数被称为默认构造函数,一般建议每个类都提供一个默认构造函数。

三、析构函数

1、析构函数概念 

 还是以栈作为例子,我们上面学习了构造函数,知道可以通过构造函数来自动完成栈的初始化工作。但是与初始化相对应的,我们每创建出一个栈,在程序结束时都得把这个栈销毁掉,这同样是一个麻烦的操作,那我们是不是可以在程序结束时让栈自动销毁呢?

为了实现这个功能,我们又引入了一个概念:析构函数。析构函数:与构造函数功能相反,析构函数不是完成对对象本身的销毁,局部对象销毁工作是在对象出了作用域时,由编译器完成的。而对象在销毁时会自动调用析构函数,完成对象中资源的清理工作

2、析构函数编写

析构函数的特性:

  1. 析构函数名是在类名前加上字符  ~ 
  2. 无参数无返回值类型
  3. 一个类只能有一个析构函数。若未显式定义,系统会自动生成默认的析构函数。注意:析构函数不能重载
  4. 对象生命周期结束时,C++编译系统系统自动调用析构函数 

 现在我们使用析构函数来编写栈的销毁:

3、默认析构函数 

与默认构造函数的性质类似。编译器生成的默认析构函数对于内置类型成员不处理,对于自定义类型成员,会调用他的析构函数。

四、拷贝构造函数

1、拷贝构造函数概念及编写

 在我们实例化一个新的对象时,如果想把这个新的对象初始化成某个已存在的对象相同的数据,则可以使用拷贝构造函数。

 拷贝构造函数:只有单个形参,该形参是对本类类型对象的引用(一般常用const修饰,一方面防止原类类型对象的数据被修改,另一方面可以接收const类型的实参),在用已存在的类类型对象创建新对象时由编译器自动调用。拷贝构造函数是构造函数的一个重载形式。

例如:

 需要注意的是,拷贝构造函数的参数只有一个且必须是类类型对象的引用,而不能直接写成类类型对象。这样做的原因是如果写成类类型对象会造成无限递归,这是编译器所不允许的。

为什么直接把拷贝构造函数的参数写成类类型对象会造成无限递归呢?我们假设写成下面的样子:

 在 main 函数中实例化类对象后,编译器会自动调用类对象的构造函数。经过函数重载的匹配,所匹配到的构造函数就是拷贝构造函数。而因为拷贝构造函数的参数是类类对象,属于传值传参,所以在调用拷贝构造函数之前需要先传参,即创建一个类类型的形参并把实参拷贝过来。但是这个创建类类型的形参的过程本身又是一个类类型对象的实例化的过程,又需要调用形参的构造函数。如此往复,就会进入无限的递归之中。

 而如果拷贝构造函数的参数是类类型对象的引用,就可以避免实参的拷贝,不需要再实例化出来一个自定义类型的形参出来了,自然也就避免了无限递归。 

2、默认拷贝构造函数

 若未显式定义,编译器会生成默认的拷贝构造函数。 默认的拷贝构造函数对象按内存存储按字节顺序完成拷贝,这种拷贝叫做浅拷贝,或者值拷贝。

 可以看到,当我们未显示定义拷贝构造函数时,编译器自动生成的拷贝构造函数仍然可以完成应有的工作。这是不是说明我们以后都不需要自己写拷贝构造函数了呢?对于日期类这样的类确实是这样的,因为在编译器生成的默认拷贝构造函数中,内置类型是按照字节方式直接拷贝的。

我们再来看下像栈这种类:

typedef int DataType;
class Stack
{
public:Stack(size_t capacity = 10){_array = (DataType*)malloc(capacity * sizeof(DataType));if (nullptr == _array){perror("malloc申请空间失败");return;}_size = 0;_capacity = capacity;}void Push(const DataType& data){// CheckCapacity();_array[_size] = data;_size++;}~Stack(){if (_array){free(_array);_array = nullptr;_capacity = 0;_size = 0;}}
private:DataType* _array;size_t _size;size_t _capacity;
};
int main()
{Stack s1;s1.Push(1);s1.Push(2);s1.Push(3);s1.Push(4);Stack s2(s1);return 0;
}

我们不写拷贝构造函数,让编译器自动生成。结果程序直接崩溃了。

这是因为日期类对象内的成员变量都是内置类型成员,直接按字节拷贝不会有任何问题。

但是栈对象不同,栈对象的数据不是单纯的存放在成员变量里面的,而是存放在堆区开辟的一块空间里的,栈对象成员变量里有一个指针指向这个堆区里开辟的空间。如果直接按字节拷贝,把对象A的内容原封不动的拷贝到对象B里去,那么两个栈对象就会指向同一块空间:

 这样会导致两个问题,其一是这两个栈对象插入删除数据会互相影响,其二是同一块空间会析构两次,导致程序崩溃。

补充内容:栈对象 s1 与 s2 都是存放在函数栈帧里面的,函数栈帧是进程里的一块空间,具有后进先出的属性,所以后定义的会先析构,所以 s2 是先析构的那一个。

 总结:类中如果没有涉及资源申请时,拷贝构造函数是否写都可以。一旦涉及到资源申请时,则拷贝构造函数是一定要写的,因为编译器生成的默认拷贝构造函数的浅拷贝无法满足我们的需求。

为了让对象有自己独自的空间,我们需要自己写拷贝构造函数来实现深拷贝:

	Stack(const Stack& st){_array = (DataType*)malloc(sizeof(DataType) * st._capacity);if (nullptr == _array){perror("malloc fail");exit(-1);}memcpy(_array, st._array, sizeof(DataType) * st._size);_size = st._size;_capacity = st._capacity;}

 此时程序就可以成功运行了。

注意:在编译器生成的默认拷贝构造函数中,内置类型是完成浅拷贝--按照字节方式直接拷贝的,而自定义类型是调用他的拷贝构造函数完成拷贝的。

 因为对象 q1、q2 中的两个成员变量是自定义类型成员,所以编译器生成的默认构造函数与默认拷贝构造函数会调用自定义类型成员的构造函数与拷贝构造函数,并且因为我们自己写了拷贝构造的深拷贝,所以新生成的四个栈都有独自的空间。

3、拷贝构造函数调用场景

  1. 使用已存在对象创建新对象
  2. 函数参数类型为类类型对象
  3. 函数返回值类型为类类型对象
class Date
{
public:Date(int year, int minute, int day){cout << "Date(int,int,int):" << this << endl;}Date(const Date& d){cout << "Date(const Date& d):" << this << endl;}~Date(){cout << "~Date():" << this << endl;}
private:int _year;int _month;int _day;
};
Date Test(Date d)
{Date temp(d);return temp;
}
int main()
{Date d1(2022, 1, 13);Test(d1);return 0;
}

 所以为了提高程序效率,一般对象传参时,尽量使用引用类型,返回时根据实际场景,能用引用
尽量使用引用。这样可以避免过多的调用拷贝构造函数。

五、赋值运算符重载

1、运算符重载概念

例如日期类对象,在实际的应用过程中,我们时常会遇到需要比较两个日期的先后、中间差了多少天等等场景。在C语言中遇到这种问题时,一般处理方法是专门写一个实现对应功能的函数,并在需要的时候调取该函数。

而在C++中,为了增强代码的可读性引入了运算符重载,运算符重载是具有特殊函数名的函数,也具有其返回值类型,函数名字以及参数列表,其返回值类型与参数列表与普通的函数类似。

 需要注意的是,我们直接使用类似于 "==" 、"+"、"-" 等运算符时,必须要调用运算符重载。因为不同于 "int"、"double" 等等内置类型编译器知道按照什么样的规则进行运算,我们编写的自定义类型对象的运算规则需要我们自己通过运算符重载来告诉编译器。

 与函数重载不同,函数重载是支持函数名相同,参数不同的函数同时使用。而运算符重载是允许自定义类型对象可以使用运算符

2、运算符重载编写

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

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

为了方便讲解,我们先暂时把类类型对象的成员变量设为公有:

 需要重载的运算符符号有几个操作数,那么运算符重载就有几个参数,如果有两个参数,第一个参数就是左操作数,第二个参数就是右操作数:

  在程序运行时, d1 == d2;这条代码会被编译器转换为去调用 operator==(d1,  d2);

在下面这种情况下,编译器会报错:

 这是因为运算符优先级的原因导致的错误,流插入 "<<"  的优先级比运算符 "==" 的优先级高,从而使程序在运行的时候报错。所以在需要打印运算符重载时,要给他加上括号:

 讲到这里,相信同学们已经明白运算符重载是什么意思以及如何实现的了,但是我们上面的写法是有问题的,因为我们为了可以在外面访问类的成员变量,把成员变量都设为了公有的。

如果现在想不改变类成员变量的访问限定符,还可以实现运算符重载,该怎么办呢?也很简单,直接把运算符重载当作类成员函数写在类里面就可以了。

不过这里有一点需要注意的地方,那就是当运算符重载在作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的 this

  此时,在程序运行时, d1 == d2;这条代码会被编译器转换为去调用 d1.operator==(d2);

 如果还需要比较日期的大于、小于等于、大于等于、不等于,就可以直接复用我们写过的等于和小于:

class Date
{
public:Date(int year = 1, 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){if (_year > d._year){return false;}else if (_month > d._month){return false;}else if (_day >= d._day){return false;}return true;}bool operator<=(const Date& d){return *this < d || *this == d;}bool operator>(const Date& d){return !(*this <= d);}bool operator>=(const Date& d){return !(*this < d);}bool operator!=(const Date& d){return !(*this == d);}//其他成员函数//....
private:int _year = 1;int _month = 1;int _day = 1;
};

总结:

  1. 不能通过连接其他符号来创建新的操作符:比如operator@
  2. 重载操作符必须有至少一个自定义类型参数
  3. 用于内置类型的运算符,其含义不能改变,例如:内置的整型 +,不能改变其含义
  4. 作为类成员函数重载时,其形参看起来比操作数数目少1,因为成员函数的第一个参数为隐藏的this
  5. .*  ::  sizeof  ?:  .  注意以上5个运算符不能重载。这个经常在笔试选择题中出现。

进阶内容,小白通篇学习之后可以再回来研究: 

现在我们再来写一个运算符重载的应用:

class Array
{
public:int& operator[](int i){assert(i < 10);return _a[i];}const int& operator[](int i) const{assert(i < 10);return _a[i];}private:int _a[10];int _size;
};void Func(const Array& aa)
{for (int i = 0; i < 10; ++i){cout << aa[i] << " ";}cout << endl;
}
int main()
{Array a;for (int i = 0; i < 10; ++i){a[i] = i;}for (int i = 0; i < 10; ++i){cout << a[i] << " ";}cout << endl;Func(a);return 0;
}

 我们把运算符 "[]" 进行运算符重载,就可以通过调用函数把一个类当成一个数组来使用。

 其中 int& operator[](int i) 函数与 const int& operator[](int i) const 构成函数重载。调用哪一个取决于我们想要怎么使用。所以一个成员函数可以既有普通成员函数,又有const成员函数。

3、赋值运算符重载

3.1、赋值运算符重载格式

想要给已存在的类对象进行赋值,可以调用赋值运算符重载:

void operator= (const Date& d){_year = d._year;_month = d._month;_day = d._day;}

 在运行时,编译器遇到代码 d2 = d1;会自动调用赋值运算符重载 d2.operator=(d1);从而完成赋值。

这种写法虽然可行,但是不好。因为我们在进行赋值操作的时候,有时需要连续赋值。形如:

x = i = j = k;

这种连续赋值的底层是从右向左赋值的。赋值表达式会先把右边的数赋值给左边的数,并把左边的数作为返回值返回,依次向左赋值。同理,为了保证赋值运算符可以连续赋值,形如:

d3 = d2 = d1;

 我们同样需要有返回值:

Date& operator=(const Date& d)
{if (this != &d){_year = d._year;_month = d._month;_day = d._day;}return *this;
}

 先把 d1 的值赋值给 d2 ,并把 d2 作为返回值返回。因为出了函数作用域之后 d2 依然存在,所以可以使用传引用返回,如此依次递推,可以从右向左全部完成赋值。

其中返回值就是为了支持连续赋值,保持运算符的特性而加进去的。

为了防止出现类似于 "d1 = d1" 这种自己给自己赋值浪费资源的情况,函数中加了一个 if 判断语句。

注意点总结:

  • 参数类型:const T&,传递引用可以提高传参效率
  • 返回值类型:T&,返回引用可以提高返回的效率,有返回值目的是为了支持连续赋值
  • if语句检测是否自己给自己赋值
  • 返回*this :要符合连续赋值的含义

补充内容:

Date d2 = d1;//拷贝构造
Date d3(d1); //拷贝构造

上面的两行代码都属于拷贝构造,而不是赋值运算符重载。赋值运算符重载是用于两个已经实例化完成的对象的,而此时 d2 还没有实例化完成,这里使用 d1 来初始化 d2 ,属于拷贝构造。

3.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)
{if (&left != &right){left._year = right._year;left._month = right._month;left._day = right._day;}return left;
}
// 编译失败:
// error C2801: “operator =”必须是非静态成员

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

以下是《C++ primer》中对于赋值运算符重载的说法:

4、默认赋值运算符重载

 用户没有在类中显式实现赋值运算符重载时,编译器会生成一个默认赋值运算符重载,以值的方式逐字节拷贝。

注意:内置类型成员变量是直接赋值的,而自定义类型成员变量需要调用对应类的赋值运算符
重载完成赋值。

 如上如所示,我们把自己写的赋值运算符重载注释掉后,依靠编译器自动生成的依然可以完成赋值。既然编译器生成的默认赋值运算符重载函数已经可以完成字节序的值拷贝了,还需要自己实
现吗?

 像日期类这样的类是没必要的,因为类中未涉及到资源管理,赋值运算符是否实现都可以。但是像栈这样涉及到资源管理的类则必须要自己实现赋值运算符重载以完成深拷贝。关于深拷贝的内容我后面会再写一篇博客专门讲解。

六、const成员

 将 const 修饰的“成员函数”称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的 this 指针,表明在该成员函数中不能对类的任何成员进行修改。

当我们写出如下代码时,编译器会报错:

class Test
{
public:void Print(){cout << _a << endl;}
private:int _a = 10;
};int main()
{const Test t;t.Print();
}

 提示不能将 “this” 指针从 “const Test” 转换为 “Test &”

这是因为我们实例化的对象 t const 类型的,而调用成员函数时所传递的 this 指针不是 const 类型的,这属于权限的放大,不被编译器所允许。

所以我们要把 this 指针也修改成 const 类型。而 this 指针是一个隐藏起来的指针,我们无法直接修改,只能通过一个间接的方式来设置。设置方式如下:

void Print() const{cout << _a << endl;}

 const 修饰 *this this 的类型变成 const Test*

此时,程序成功运行。

所以对于内部不改变成员变量的成员函数,最好加上 const ,这样 const对象 与 普通对象 都可以调用该函数。

七、取地址及const取地址操作符重载

这两种成员函数一般不用我们自己去定义,编译器默认会生成。

 我们没有定义取地址及const取地址操作符重载,但是编译器默认生成的函数已经完全可以满足我们的使用需求了。

当然如果非要自己写也可以自己来定义编写,不过没有什么价值:

Test* operator&() //取地址操作符重载
{return this;
}const Test* operator&() const //const取地址操作符重载
{return this;
}

 如果大家闲得无聊的话也可以硬是给他们创造价值,比如不希望别人使用操作符 "&" 来取地址的时候,我们就可以返回一个假的地址给操作者:

Test* operator&() //取地址操作符重载
{return nullptr;
}const Test* operator&() const //const取地址操作符重载
{return nullptr;
}

关于类和对象第二部分的内容就讲到这里,希望同学们多多支持,如果有不对的地方欢迎大佬指正,谢谢! 

相关文章:

【C++】类和对象(二)

目录 一、默认成员函数 二、构造函数 1、构造函数概念 2、构造函数编写 3、默认构造函数 4、内置类型成员的补丁 三、析构函数 1、析构函数概念 2、析构函数编写 3、默认析构函数 四、拷贝构造函数 1、拷贝构造函数概念及编写 2、默认拷贝构造函数 3、拷贝构造…...

UDP协议

文章目录一、前沿知识应用层传输层二、UDP协议一、前沿知识 应用层 应用层&#xff1a;描述了应用程序如何理解和使用网络中的通信数据。 我们程序员在应用层的主要工作是自定义协议&#xff0c;因为下面四层都在系统内核/驱动程序/硬件中已经实现好了&#xff0c;不能去修改…...

IT人的晋升之路——关于人际交往能力的培养

对于咱们的程序员来说&#xff0c;工作往往不是最难的&#xff0c;更难的是人际交往和关系的维护处理。很多时候我们都宁愿加班&#xff0c;也不愿意是社交&#xff0c;认识新的朋友&#xff0c;拓展自己的圈子。对外的感觉就好像我们丧失了人际交往能力&#xff0c;是个呆子&a…...

Docker进阶 - 8. docker network 网络模式之 container

目录 1. container 模式概述 2. 使用Alpine操作系统来验证 container 模式 1. container 模式概述 container网络模式新建的容器和已经存在的一个容器共享一个网络ip配置而不是和宿主机共享。新创建的容器不会创建自己的网卡&#xff0c;配置自己的IP&#xff0c;而是和一个…...

2年功能测试月薪9.5K,100多天自学自动化,跳槽涨薪4k后我的路还很长...

前言 其实最开始我并不是互联网从业者&#xff0c;是经历了一场六个月的培训才入的行&#xff0c;这个经历仿佛就是一个遮羞布&#xff0c;不能让任何人知道&#xff0c;就算有面试的时候被问到你是不是被培训的&#xff0c;我还是不能承认这段历史。我是为了生存&#xff0c;…...

“数字孪生”:为什么要仿真嵌入式系统?

​01.仿真是什么&#xff1f; 仿真的概念非常广泛&#xff0c;但归根结底都是使用可控的手段来模仿真实的情况&#xff0c;通常应用于现实世界中实施难度大甚至是无法实践的事物。 众所周知&#xff0c;嵌入式系统通常是形式多样的、面向特定应用的软硬件综合体&#xff0c;无…...

Java基础知识总结(上)

Java基础知识总结 1. Java语言的特点 简单易学&#xff0c;相较于python等语言具有较好的严谨性以及报错机制&#xff1b; 面向对象&#xff08;封装&#xff0c;继承&#xff0c;多态&#xff09;&#xff0c;Java中所有内容都是基于类进行扩展的&#xff0c;由类创建的实体…...

MySQL 2:MySQL约束

一、定义 约束&#xff08;constraint&#xff09;&#xff0c;即表中数据的限制条件。在表设计中加入约束的目的是保证表中记录的完整性和有效性。 比如user表&#xff0c;有些列&#xff08;手机号&#xff09;的值不能为空&#xff0c;有些列&#xff08;身份证号&#xff…...

C4--Vivado添加列表中不存在的FLash器件2023-02-10

以华邦SPI FLASH W25Q128JVEIQ为例进行说明。&#xff08;其他Flash添加步骤一致&#xff09; 1.本地vivado安装目录D:\Softwares\xlinx_tools\Vivado\2020.2\data\xicom下&#xff0c;找到xicom_cfgmem_part_table.csv文件&#xff0c;这个表与vivado hardware manager中的器…...

php代码审计

准备工作 了解CMS的基本信息 该CMS使用的是什么设计模式&#xff1f;该CMS每个目录大概负责的功能(视图、缓存、控制器等)。该CMS处理请求的基本流程是如何走的&#xff1f;以及在系统中使用的全局过滤函数是如何对数据进行处理的&#xff1f; 代码审计方法 敏感函数回溯 …...

接口测试入门,如何划分接口文档

1.首先最主要的就是要分析接口测试文档&#xff0c;每一个公司的测试文档都是不一样的。具体的就要根据自己公司的接口而定&#xff0c;里面缺少的内容自己需要与开发进行确认。 我认为一针对于测试而言的主要的接口测试文档应该包含的内容分为以下几个方面。 a.具体的一个业…...

数据库学习第二天

第7章 系统预定义函数 函数&#xff1a;代表一个独立的可复用的功能。 和Java中的方法有所不同&#xff0c;不同点在于&#xff1a;MySQL中的函数必须有返回值&#xff0c;参数可以有可以没有。 MySQL中函数分为&#xff1a; &#xff08;1&#xff09;系统预定义函数&…...

NODE => CORS跨域资源共享学习

1.CORS跨域资源共享 cors是Express的一个第三方中间件。通过安装和配置cors中间件&#xff0c;可以很方便地解决跨域问题 运行npm install cors 安装中间件使用const cors require(‘cors’) 导入中间件在路由之前调用 app.use&#xff08;cors&#xff08;&#xff09;&#…...

golang rabbitMQ 生产者复用channel以及生产者组分发策略

引用的是rabbitMQ官方示例的库&#xff1a;github.com/rabbitmq/amqp091-go在网络编程中我们知道tcp连接的创建、交互、销毁等相关操作的"代价"都是很高的&#xff0c;所以就要去实现如何复用这些连接&#xff0c;并要做到高效并可靠。预期效果&#xff1a;项目初始化…...

掌握了这项技能的性能测试师,90%都升职加薪了

初入职场的新人该怎么做才能让自己快速成长&#xff1f;在公司一直做着手工测试&#xff0c;如何才能提升自己&#xff0c;避免陷入“只涨年龄不涨经验”的尴尬&#xff1f;做为一名软件测试工程师&#xff0c;我们不得不去面对这些问题&#xff0c;有的人找到了答案&#xff0…...

linux中crontab定时任务导致磁盘满和云监控未报警的的坑

一个后台开发者&#xff0c;兼职运维工作中&#xff0c;配置linux中crontab定时任务&#xff0c;导致磁盘满和云监控未报警的问题的坑。 1.磁盘满 使用命令 df -h2.问题排查 2.1排查日志 命令 cat /var/log/messages日志文件的默认路径是&#xff1a;/var/log 下面是几个…...

vscode中安装python运行调试环境

在运行代码之前&#xff0c;需要到微软商店下载安装python环境&#xff0c;35m&#xff0c;都是自动的。 1、安装python 的extensions插件。 ctrlshiftx 输入 python 后点击 install 按钮。 2、新建文件夹spider文件夹。 3、在新建文件夹spider下新建文件spider.py源代码。…...

【微服务】微服务架构超强讲解,通俗易懂

微服务架构目录一、微服务架构介绍二、出现和发展三、传统开发模式和微服务的区别四、微服务的具体特征五、面向服务的架构SOA&#xff08;service oriented architecture&#xff09;和微服务的区别1、SOA喜欢重用&#xff0c;微服务喜欢重写2、SOA喜欢水平服务&#xff0c;微…...

内核中的竞态产生的原因和解决方法

产生原因&#xff1a; 由于多进程对临界资源的抢占 根本原因&#xff1a; 1、对于单核处理器而言&#xff0c;内核支持抢占就会出现竞态 2、对于多核处理器而言&#xff0c;是核与核的竞态 3、进程与中断间存在竞态 4、arm开发板不会出现中断与中断间的竞态&#xff08;目前&am…...

【微服务】Elasticsearch文档索引库操作(二)

&#x1f697;Es学习第二站~ &#x1f6a9;Es学习起始站&#xff1a;【微服务】Elasticsearch概述&环境搭建(一) &#x1f6a9;本文已收录至专栏&#xff1a;微服务探索之旅 &#x1f44d;希望您能有所收获 一.索引库操作 索引库就类似数据库表&#xff0c;mapping映射就类…...

【论文速递】NAACL2022-DEGREE: 一种基于生成的数据高效事件抽取模型

【论文速递】NAACL2022-DEGREE: 一种基于生成的数据高效事件抽取模型 【论文原文】&#xff1a;DEGREE A Data-Efficient Generation-Based Event Extraction Mode 【作者信息】&#xff1a;I-Hung Hsu &#xff0c; Kuan-Hao Huang&#xff0c; Elizabeth Boschee &#xff…...

C++类和对象(下)

✨个人主页&#xff1a; Yohifo &#x1f389;所属专栏&#xff1a; C修行之路 &#x1f38a;每篇一句&#xff1a; 图片来源 I do not believe in taking the right decision. I take a decision and make it right. 我不相信什么正确的决定。我都是先做决定&#xff0c;然后把…...

Java常见的六种线程池、线程池-四种拒绝策略总结

点个关注&#xff0c;必回关 一、线程池的四种拒绝策略&#xff1a; CallerRunsPolicy - 当触发拒绝策略&#xff0c;只要线程池没有关闭的话&#xff0c;则使用调用线程直接运行任务。 一般并发比较小&#xff0c;性能要求不高&#xff0c;不允许失败。 但是&#xff0c;由于…...

Node=>Express中间件分类 学习4

1.中间件分类 应用级别的中间件路由级别的中间件错误级别的中间件Express 内置的中间件第三方的中间件 通过app.use&#xff08;&#xff09;或app.get&#xff08;&#xff09;或app.post&#xff08;&#xff09;绑定到app实力上的中间件&#xff0c;叫做应用级别的中间件 …...

在阿里当外包,是一种什么工作体验?

上周和在阿里做外包的朋友一起吃饭&#xff0c;朋友吃着吃着&#xff0c;就开启了吐槽模式。 他一边喝酒一边说&#xff0c;自己现在做着这份工作&#xff0c;实在看不到前途。 看他状态不佳&#xff0c;问了才知道&#xff0c;是手上的项目太磨人。 他们现在做的项目&#…...

Vue3快速入门【二】

Vue3快速入门一、传值父传子&#xff0c;子传父v-model二、插槽2.1、匿名插槽2.2、具名插槽2.3、插槽作用域2.4、插槽作用域案例2.4.1、初始布局2.4.2、插槽使用2.4.3、点击编辑按钮获取本行数据&#xff08;插槽作用域的使用&#xff09;2.4.4、类型书写优化2.4.5、全局接口抽…...

C++-类和对象(上)

类和对象&#xff08;上&#xff09;一&#xff0c;构造函数1&#xff0c;概念2&#xff0c;特性二&#xff0c;析构函数1&#xff0c;概念2&#xff0c;特性三&#xff0c;拷贝构造1&#xff0c;概念2&#xff0c;特性四&#xff0c;运算符重载1&#xff0c;概念2&#xff0c;…...

CAPL(vTESTStudio) - DoIP - TCP接收_04

TCP接收 函数介绍 TcpOpen函数...

联合培养博士经历对于国内就业有优势吗?

2023年国家留学基金委&#xff08;CSC&#xff09;申请在即&#xff0c;很多在读博士在关心申报的同时&#xff0c;也对联培经历能否有助于国内就业心中存疑&#xff0c;故此知识人网小编重点解答此问题。之前&#xff0c;我们在“CSC联合培养-国内在读博士出国的绝佳选择”一文…...

测试左移之需求质量

测试左移的由来 缺陷的修复成本逐步升高 下面是质量领域司空见惯的一张图&#xff0c;看图说话&#xff0c;容易得出&#xff1a;大部分缺陷都是早期引入的&#xff0c;同时大部分缺陷都是中晚期发现的&#xff0c;而缺陷发现的越晚&#xff0c;其修复成本就越高。因此&#…...

wordpress访客明细/宁波seo行者seo09

我们在Linux服务器中搭建建站系统较为多见的是利用Nginx或者是Apache&#xff0c;这个应该是占用大部分网站站长使用的WEB引擎。但是&#xff0c;也有很多网友会选择其他引擎环境的&#xff0c;比如我们熟知的还有Litespeed、Lighttpd&#xff0c;以及其他多种引擎方式。其实这…...

不通过第三方平台做微网站/seo优化培训课程

安装MYSQL1. 卸载已有mysql查看是否已安装mysql&#xff1a;rpm -qa mysql有则卸载&#xff1a;rpm -e --nodeps 文件名称是否存在与mysql相关的文件或目录&#xff1a;whereis mysql是则删除。查看是否存在mariadb&#xff1a;rpm -qa | grep mariadb存在则卸载&#xff1a;rp…...

租一个网站服务器多少钱/网站百度不收录的原因

java打印正金字塔&#xff0c;倒金字塔和“水影”金字塔 -------哒哒~~~~~~~~~~ 小小少年闲来无事&#xff0c;想起自己初学java的时候做的经典的无非就是打印出一些有意思的图形&#xff0c;心血来潮自己就写了这么一个打印金字塔的demo&#xff0c;自己回顾一下当初的感受&am…...

怎么对网站链接做拆解/网站群发推广软件

记得两年前在山东济南的一家加油站&#xff0c;发生这么一件事情让其在网上火爆了一把&#xff0c;同时还惊动了当地质监局&#xff0c;事情是这样子的&#xff1a;一车主在油表剩八分一的时候去加油&#xff0c;他预估加油量最多也就38升&#xff0c;结果加满油后加出了41.7升…...

企业邮箱域名怎么设置/昆明自动seo

写在最前 &#xff0c;刚结束完秋招&#xff0c;大大小小的公司参加笔试的大概有30个左右&#xff0c;最终也算是收获了自己比较满意的offer,特此来为大家分享一波笔试的经验&#xff0c;望诸君共勉。1.笔试是什么&#xff1f;关于校招笔试&#xff0c;首先你需要知道笔试的题型…...

效果建网站的公/安徽百度seo教程

1M411050 了解计算机应用和自动控制的基础知识复习要点1.计算机应用的基本知识2.自动控制的基本原理及系统的组成单项选择题1.WINDOWS是计算机常用的( )。A.应用软件B.数据库管理系统C.系统软件D.服务程序2.计算机的内存容量越大&#xff0c;计算机( )。A.运算速度越快B.运算精…...