c++ 基本语法:https://www.w3cschool.cn/cpp/
c++初始化参数列表:https://blog.csdn.net/coder_xia/article/details/7447822
c++默认构造函数:https://www.cnblogs.com/gklovexixi/p/5814626.html
c++ 复制构造函数:
三 复制控制
1 复制构造函数
1.1 几个要点
1.1 几个要点
(1) 复制构造函数
复制构造函数是一种特殊构造函数,只有1个形参,该形参(常用 const &修饰)是对该类类型的引用。
class Peopel
{
public:
Peopel();//默认构造函数
Peopel(const Peopel&);//复制构造函数
~Peopel();//析构函数
};
当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。Peopel a1; Peopel a2 = a1;
当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用复制构造函数。
Peopel Func(Peopel b){...}
(2)析构函数
析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。
析构函数可用于释放构造对象时或在对象的生命期中所获取的资源。
不管类是否定义了自己的析构函数,编译器都自动执行类中非 static 数据成员的析构函数。
(3) 复制控制
(3) 复制控制
复制构造函数、赋值操作符和析构函数总称为复制控制。编译器自动实现这些操作,但类也可以定义自己的版本。
(4) 两种初始化形式
C++ 支持两种初始化形式:直接初始化和复制初始化。直接初始化将初始化式放在圆括号中,复制初始化使用 = 符号。
对于内置类型,例如int, double等,直接初始化和复制初始化没有区别。
对于类类型:直接初始化直接调用与实参匹配的构造函数;复制初始化先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。直接初始化比复制初始化更快。
(5)形参和返回值
对于内置类型,例如int, double等,直接初始化和复制初始化没有区别。
对于类类型:直接初始化直接调用与实参匹配的构造函数;复制初始化先使用指定构造函数创建一个临时对象,然后用复制构造函数将那个临时对象复制到正在创建的对象。直接初始化比复制初始化更快。
(5)形参和返回值
当形参或返回值为类类型时,由该类的复制构造函数进行复制。
(6)初始化容器元素
复制构造函数可用于初始化顺序容器中的元素。例如:
vector<string> svec(5);
编译器首先使用 string 默认构造函数创建一个临时值,然后使用复制构造函数将临时值复制到 svec 的每个元素。
(7)构造函数与数组元素
vector<string> svec(5);
编译器首先使用 string 默认构造函数创建一个临时值,然后使用复制构造函数将临时值复制到 svec 的每个元素。
(7)构造函数与数组元素
如果没有为类类型数组提供元素初始化式,则将用默认构造函数初始化每个元素。
如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素:
Sales_item primer_eds[] = { string("0-201-16487-6"),
string("0-201-54848-8"),
string("0-201-82470-1"),
Sales_item()
};
如果使用常规的花括号括住的数组初始化列表来提供显式元素初始化式,则使用复制初始化来初始化每个元素。根据指定值创建适当类型的元素,然后用复制构造函数将该值复制到相应元素:
Sales_item primer_eds[] = { string("0-201-16487-6"),
string("0-201-54848-8"),
string("0-201-82470-1"),
Sales_item()
};
1.2 合成的复制构造函数
(1)合成的复制构造函数
如果没有定义复制构造函数,编译器就会为我们合成一个。
合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。
逐个成员初始化:合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。
例外:如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。
如果没有定义复制构造函数,编译器就会为我们合成一个。
合成复制构造函数的行为是,执行逐个成员初始化,将新对象初始化为原对象的副本。
逐个成员初始化:合成复制构造函数直接复制内置类型成员的值,类类型成员使用该类的复制构造函数进行复制。
例外:如果一个类具有数组成员,则合成复制构造函数将复制数组。复制数组时合成复制构造函数将复制数组的每一个元素。
1.3 定义自己的复制构造函数
(1) 只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义复制构造函数,也可以复制。
class Peopel
{
public:
std::string name;
unsigned int id;
unsigned int age;
std::string address;
};
(2) 有些类必须对复制对象时发生的事情加以控制。 例如,类有一个数据成员是指针,或者有成员表示在构造函数中分配的其他资源。而另一些类在创建新对象时必须做一些特定工作。这两种情况下,都必须定义自己的复制构造函数。
最好显式或隐式定义默认构造函数和复制构造函数。如果定义了复制构造函数,必须定义默认构造函数。
最好显式或隐式定义默认构造函数和复制构造函数。如果定义了复制构造函数,必须定义默认构造函数。
1.4 禁止复制
有些类需要完全禁止复制。例如,iostream 类就不允许复制。延伸:容器内元素不能为iostream
为了防止复制,类必须显式声明其复制构造函数为 private。
有些类需要完全禁止复制。例如,iostream 类就不允许复制。延伸:容器内元素不能为iostream
为了防止复制,类必须显式声明其复制构造函数为 private。
2 赋值操作符
与复制构造函数一样,如果类没有定义自己的赋值操作符,则编译器会合成一个。
与复制构造函数一样,如果类没有定义自己的赋值操作符,则编译器会合成一个。
(1)重载赋值操作符
Sales_item& operator=(const Sales_item &);
(2)合成赋值操作符
(2)合成赋值操作符
合成赋值操作符会逐个成员赋值:右操作数对象的每个成员赋值给左操作数对象的对应成员。除数组之外,每个成员用所属类型的常规方式进行赋值。对于数组,给每个数组元素赋值。
(3)复制和赋值常一起使用
一般而言,如果类需要复制构造函数,它也会需要赋值操作符。
一般而言,如果类需要复制构造函数,它也会需要赋值操作符。
四 友元
友元机制允许一个类将对其非公有成员的访问权授予指定的函数或类。
友元可以出现在类定义的内部的任何地方。
友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。
友元不是授予友元关系的那个类的成员,所以它们不受声明出现部分的访问控制影响。
建议:将友元声明成组地放在类定义的开始或结尾。
1 友元类
class Husband
{
public:
friend class Wife;
private:
double money;//钱是老公私有的,别人不能动,但老婆除外
};
class Wife
{
public:
void Consume(Husband& h)
{
h.money -= 10000;//老婆可以花老公的钱
}
};
Husband h;
Wife w;
w.Consume(h);
2 使其他类的成员函数成为友元
class Husband; //1.声明Husband
class Wife //2.定义Wife类
{
public:
void Consume(Husband& h);
};
class Husband //3.定义Husband类
{
public:
friend void Wife::Consume(Husband& h);//声明Consume函数。
private:
double money;//钱是老公私有的,别人不能动,但老婆除外
};
void Wife::Consume(Husband& h) //4.定义Consume函数。
{
h.money -= 10000;//老婆可以花老公的钱
}
注意类和函数的声明和定义的顺序:
(1)声明类Husband
(2)定义类Wife,声明Consume函数
(3)定义类Husband
(4)定义Consume函数。
五 static 类成员
static 成员,有全局对象的作用,但又不破坏封装。
1 static 成员变量
static 数据成员是与类关联的对象,并不与该类的对象相关联。
static 成员遵循正常的公有/私有访问规则。
static 成员遵循正常的公有/私有访问规则。
2 使用 static 成员而不是全局对象有三个优点。
(1) static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
(2) 可以实施封装。static 成员可以是私有成员,而全局对象不可以。
(3) 通过阅读程序容易看出 static 成员是与特定类关联的,这种可见性可清晰地显示程序员的意图。
(1) static 成员的名字是在类的作用域中,因此可以避免与其他类的成员或全局对象名字冲突。
(2) 可以实施封装。static 成员可以是私有成员,而全局对象不可以。
(3) 通过阅读程序容易看出 static 成员是与特定类关联的,这种可见性可清晰地显示程序员的意图。
3 static 成员函数
在类的内部声明函数时需要添加static关键字,但是在类外部定义函数时就不需要了。
因为static 成员是类的组成部分但不是任何对象的组成部分,所以有以下几个特点:
1) static 函数没有 this 指针
2) static 成员函数不能被声明为 const (将成员函数声明为 const 就是承诺不会修改该函数所属的对象)
在类的内部声明函数时需要添加static关键字,但是在类外部定义函数时就不需要了。
因为static 成员是类的组成部分但不是任何对象的组成部分,所以有以下几个特点:
1) static 函数没有 this 指针
2) static 成员函数不能被声明为 const (将成员函数声明为 const 就是承诺不会修改该函数所属的对象)
3) static 成员函数也不能被声明为虚函数
4 static 数据成员
static 数据成员可以声明为任意类型,可以是常量、引用、数组、类类型,等等。
static 数据成员必须在类定义体的外部定义(正好一次),并且应该在定义时进行初始化。
static 数据成员可以声明为任意类型,可以是常量、引用、数组、类类型,等等。
static 数据成员必须在类定义体的外部定义(正好一次),并且应该在定义时进行初始化。
建议:定义在类的源文件中名,即与类的非内联函数的定义同一个文件中。注意,定义时也要带上类类型+"::"
double Account::interestRate = 0.035;
double Account::interestRate = 0.035;
5 特殊的静态常量整型成员
静态常量整型数据成员可以直接在类的定义体中进行初始化,例如:
static const int period = 30;
静态常量整型数据成员可以直接在类的定义体中进行初始化,例如:
static const int period = 30;
当然char 可以转换成整形,也是可以的, static const char bkground = '#';
6 其他
(1)static 数据成员的类型可以是该成员所属的类类型。非 static 成员只能是自身类对象的指针或引用
class Screen
(1)static 数据成员的类型可以是该成员所属的类类型。非 static 成员只能是自身类对象的指针或引用
class Screen
{
public:
// ...
private:
static Screen src1; // ok
public:
// ...
private:
static Screen src1; // ok
Screen *src2; // ok
Screen src3; // error
};
(2)非 static 数据成员不能用作默认实参,static 数据成员可用作默认实参
(2)非 static 数据成员不能用作默认实参,static 数据成员可用作默认实参
class Screen
{
public:
Screen& clear(char = bkground);
private:
static const char bkground = '#';//static const整形变量可以在类内部初始化。
};
public:
Screen& clear(char = bkground);
private:
static const char bkground = '#';//static const整形变量可以在类内部初始化。
};