C++ Prime 第七章 类

2023-03-29~2023-2023-03-31

7.1.1节练习

alt

  • 练习7.1:略

定义在类内部的函数是隐式的inline函数

this指针

  • 调用成员方法时,编译会将成员对象当作实参隐式的传递给成员函数中隐式的this形参,例子如下:
Sales_data total;
total.isbn(); // 实际上编译器做了这些操作Sales_data::isbn(total);
  • 在成员函数之后加上const关键字,代表const this,这样的成员函数成为常量成员函数,常量成员函数可以读取成员对象的值但无法修改
  • 常量对象只能调用常量成员函数

类作用域和成员函数

  • 编译器分两步处理类,先编译成员的声明,再编译成员函数体,所以成员函数体内先定义的成员对象调用后定义的成员对象时,不会存在未定义的问题

7.1.2节练习

alt

  • 练习7.2:
class Sales_data
{
public:
	string bookNo;
	unsigned units_sold;
	double revenue;

	string isbn() const
	{
		return this->bookNo;
	}

	Sales_data& combine(Sales_data& temp)
	{
		this->revenue += temp.revenue;
		this->units_sold += temp.units_sold;
		return *this;
	}
};
  • 练习7.3:
#include<iostream>
using namespace std;

class Sales_data
{
	friend istream& operator>>(istream& cin, Sales_data& s)
	{
		double price;
		cout << "bookNo:" << endl;
		cin >> s.bookNo;
		cout << "units_sold:" << endl;
		cin >> s.units_sold;
		cout << "price:" << endl;
		cin >> price;
		s.revenue = price * s.units_sold;
		return cin;
	}
public:
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;

	string isbn() const
	{
		return this->bookNo;
	}

	Sales_data& combine(Sales_data& temp)
	{
		this->revenue += temp.revenue;
		this->units_sold += temp.units_sold;
		return *this;
	}
};

int main()
{
	Sales_data total;
	if (cin >> total)
	{
		Sales_data trans;
		while (cin >> trans)
		{
			if (total.isbn() == trans.isbn())
			{
				total.combine(trans);
			}
			else
			{
				cout << "bookNo:" << total.bookNo << endl;
				cout << "revenue:" << total.revenue << endl;
				cout << "units_sold:" << total.units_sold << endl;
				total = trans;
			}
		}
		cout << "bookNo:" << total.bookNo << endl;
		cout << "revenue:" << total.revenue << endl;
		cout << "units_sold:" << total.units_sold << endl;
	}
	else
	{
		cout << "No data" << endl;
	}
	system("pause");
	return 0;
}

  • 练习7.4:
class Person
{
public:
	string myName;
	string address;
};
  • 练习7.5:需要加上const,获取名字或者地址并不涉及到修改操作可以加上const限定符
#include<iostream>
using namespace std;

class Person
{
public:
	string myName;
	string address;

	const string& getName() const
	{
		return this->myName;
	}

	const string& getAdress() const
	{
		return this->address;
	}
};

int main()
{
	system("pause");
	return 0;
}

7.1.3节练习

alt

  • 练习7.6:
class Sales_data
{
public:
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;

	string isbn() const
	{
		return this->bookNo;
	}

	Sales_data& combine(Sales_data& temp)
	{
		this->revenue += temp.revenue;
		this->units_sold += temp.units_sold;
		return *this;
	}

	double avg_price() const
	{
		return this->revenue / this->units_sold;
	}

	istream& read(istream* is, Sales_data& s)
	{
		double price;
		cout << "bookNo:" << endl;
		cin >> s.bookNo;
		cout << "units_sold:" << endl;
		cin >> s.units_sold;
		cout << "price:" << endl;
		cin >> price;
		s.revenue = price * s.units_sold;
		return cin;
	}
	ostream& print(ostream& os, const Sales_data& s)
	{
		os << s.isbn() << " " << s.units_sold << " "
			<< s.revenue << " " << s.avg_price();
		return os;
	}

	Sales_data add(Sales_data& s1, Sales_data& s2)
	{
		Sales_data sum = s1;
		s1.combine(s2);
		return sum;
	}

};
  • 练习7.7:
#include<iostream>
using namespace std;

class Sales_data
{
public:
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;

	string isbn() const
	{
		return this->bookNo;
	}

	Sales_data& combine(Sales_data& temp)
	{
		this->revenue += temp.revenue;
		this->units_sold += temp.units_sold;
		return *this;
	}

	double avg_price() const
	{
		return this->revenue / this->units_sold;
	}
};

istream& read(istream& is, Sales_data& s)
{
	double price;
	cout << "bookNo:" << endl;
	cin >> s.bookNo;
	cout << "units_sold:" << endl;
	cin >> s.units_sold;
	cout << "price:" << endl;
	cin >> price;
	s.revenue = price * s.units_sold;
	return cin;
}
ostream& print(ostream& os, const Sales_data& s)
{
	os << s.isbn() << " " << s.units_sold << " "
		<< s.revenue << " " << s.avg_price();
	return os;
}

Sales_data add(Sales_data& s1, Sales_data& s2)
{
	Sales_data sum = s1;
	s1.combine(s2);
	return sum;
}

int main()
{
	Sales_data total;
	if (read(cin, total))
	{
		Sales_data trans;
		while (read(cin, trans))
		{
			if (total.isbn() == trans.isbn())
			{
				total.combine(trans);
			}
			else
			{
				print(cout, total);
				total = trans;
			}
		}
		print(cout, total);
	}
	else
	{
		cout << "No data" << endl;
	}
	system("pause");
	return 0;
}
  • 练习7.8:read中涉及到变量的赋值,print没有涉及到变量的修改
  • 练习7.9:
class Person
{
public:
	string myName;
	string address;
};

ostream& print(ostream& os, Person& s)
{
	cout << s.myName << " " << s.address << " " << endl;
}

istream& read(istream& is, Person& s)
{
	cout << "Name" << endl;
	cin >> s.myName;
	cout << "Adress" << endl;
	cin >> s.address;
	return is;
}
  • 练习7.1:判断read(cin, data1)是否有输入流,如果read(cin, data1)没有输入流,不会返回istream&,会导致外层的read(read(cin, data1), data2)没有输入流,就会返回false

构造函数

  • 构造函数:类通过几个特殊的函数进行初始化操作的过程,这些特殊的函数被称为构造函数,为什么称为这些?因为构造函数可以被重载所以构造函数可以不只有一个
  • 构造函数不能被const修饰,当我们创建一个常量对象时,对象要先调用构造函数进行初始化,然后才会获得常量属性
  • 每个类都会隐式的默认创建一个构造函数,该函数被称为合成默认构造函数,该函数无需实参,如果我们定义类时显示的定义了任何的构造函数或者定义中包含了一个另一个类的类类型并且这个类类型定义了构造函数,那么合成默认构造函数将不会被创建,此时需要我们手动创建合成默认构造函数
  • 默认构造函数对未提供初始值的成员会进行默认初始化操作,如下会对name进行默认初始化为空字符串
class Person
{
  string name;
  int age = 10;
}
  • 创建默认构造函数可以使用default关键字
class Person
{
  Person() = dafault; // 在类的内部使用default该默认构造函数是内联的
  string name;
  int age = 10;
}
Person() = default; // 在类的外部使用default该默认构造函数不是内联的

构造函数初始值列表

class Person
{
public:
	string myName;
	int myAge = 10;
	Person() = default;
	Person(string& name) :myName(name) {};
};

7.1.4节练习

alt

  • 练习7.11:
#include<iostream>
using namespace std;

class Sales_data
{
public:

	Sales_data() = default;
	Sales_data(string& bNo) :bookNo(bNo) {};
	Sales_data(istream& is);
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;

	string isbn() const
	{
		return this->bookNo;
	}

	Sales_data& combine(Sales_data& temp)
	{
		this->revenue += temp.revenue;
		this->units_sold += temp.units_sold;
		return *this;
	}

	double avg_price() const
	{
		return this->revenue / this->units_sold;
	}
};

istream& read(istream& is, Sales_data& s)
{
	double price;
	cout << "bookNo:" << endl;
	cin >> s.bookNo;
	cout << "units_sold:" << endl;
	cin >> s.units_sold;
	cout << "price:" << endl;
	cin >> price;
	s.revenue = price * s.units_sold;
	return cin;
}

Sales_data::Sales_data(istream& is)
{
	read(is, *this);
}

int main()
{
	Sales_data s1();
	string name = "kangkang";
	Sales_data s2(name);
	Sales_data s3(cin);
	system("pause");
	return 0;
}

  • 练习7.12:
class Sales_data
{
public:
	friend istream& read(istream&, Sales_data&);
	Sales_data() = default;
	Sales_data(string& bNo) :bookNo(bNo) {};
	Sales_data(istream& is)
	{
		read(is, *this);
	};
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;

	string isbn() const
	{
		return this->bookNo;
	}

	Sales_data& combine(Sales_data& temp)
	{
		this->revenue += temp.revenue;
		this->units_sold += temp.units_sold;
		return *this;
	}

	double avg_price() const
	{
		return this->revenue / this->units_sold;
	}
};

istream& read(istream& is, Sales_data& s)
{
	double price;
	cout << "bookNo:" << endl;
	is >> s.bookNo;
	cout << "units_sold:" << endl;
	is >> s.units_sold;
	cout << "price:" << endl;
	is >> price;
	s.revenue = price * s.units_sold;
	return is;
}
  • 练习7.13:
#include<iostream>
using namespace std;


class Sales_data
{
public:
	friend istream& read(istream&, Sales_data&);
	Sales_data() = default;
	Sales_data(string& bNo) :bookNo(bNo) {};
	Sales_data(istream& is)
	{
		read(is, *this);
	};
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;

	string isbn() const
	{
		return this->bookNo;
	}

	Sales_data& combine(Sales_data& temp)
	{
		this->revenue += temp.revenue;
		this->units_sold += temp.units_sold;
		return *this;
	}

	double avg_price() const
	{
		return this->revenue / this->units_sold;
	}
};

istream& read(istream& is, Sales_data& s)
{
	double price;
	cout << "bookNo:" << endl;
	is >> s.bookNo;
	cout << "units_sold:" << endl;
	is >> s.units_sold;
	cout << "price:" << endl;
	is >> price;
	s.revenue = price * s.units_sold;
	return is;
}


ostream& print(ostream& os, const Sales_data& s)
{
	os << s.isbn() << " " << s.units_sold << " "
		<< s.revenue << " " << s.avg_price();
	return os;
}

Sales_data add(Sales_data& s1, Sales_data& s2)
{
	Sales_data sum = s1;
	s1.combine(s2);
	return sum;
}

int main()
{
	Sales_data total(cin);
	if (total.bookNo != "")
	{
		while (cin)
		{
			Sales_data trans(cin);
			if (total.isbn() == trans.isbn())
			{
				total.combine(trans);
			}
			else
			{
				print(cout, total) << endl;
				total = trans;
			}
		}
		print(cout, total) << endl;
	}
	else
	{
		cerr << "No data" << endl;
	}
	system("pause");
	return 0;
}

  • 练习7.14~7.15:
class Person
{
public:
	string myName;
	int myAge = 10;
	Person() = default;
	Person(string& name) :myName(name) {};
};

class和struct关键字

  • class和struct定义类的唯一区别就是默认访问权限,class默认是私有,struct默认是公共

7.2节练习

alt

  • 练习7.16:位置必须出现在类内,次数没有限定,对外提供的接口在public中,数据成员和具体实现在private中
  • 练习7.17:默认访问权限不同,class默认是私有,struct默认是公共
  • 练习7.18:可以提供程序的安全性和可靠性。通过避免数据被随意修改,来保证数据有效性和一致性,避免外部代码可以直接访问封装后的代码降低耦合性。
  • 练习7.19:如下:原因如上练习7.18
class Person
{
public:
	Person() = default;
	Person(string& name) :myName(name) {};
private:
	string myName;
	int myAge = 10;
};

友元

  • 将某些类或者函数进行友元声明后,就允许其他类或者函数访问自己的非公有成员
  • 友元声明仅仅指定了访问权限,而非通常意义上的函数声明如果我们希望用户能够调用某个友员函数,那么必须在友元声明之外再声明函数一次

7.2.1节练习

alt

  • 练习7.20:允许其他类或者函数访问自己非共有成员,提高程序的灵活性,但是提高灵活性的结果就是代码耦合性提高、数据安全性降低
  • 练习7.21:
#include<iostream>
using namespace std;


class Sales_data
{
	friend istream& read(istream&, Sales_data&);
	friend ostream& print(ostream& os, const Sales_data& s);
public:
	Sales_data() = default;
	Sales_data(string& bNo) :bookNo(bNo) {};
	Sales_data(istream& is)
	{
		read(is, *this);
	};
	string isbn() const
	{
		return this->bookNo;
	}
	Sales_data& combine(Sales_data& temp)
	{
		this->revenue += temp.revenue;
		this->units_sold += temp.units_sold;
		return *this;
	}
private:
	string bookNo;
	unsigned units_sold = 0;
	double revenue = 0;
	double avg_price() const
	{
		return this->revenue / this->units_sold;
	}
};

istream& read(istream& is, Sales_data& s)
{
	double price;
	cout << "bookNo:" << endl;
	is >> s.bookNo;
	cout << "units_sold:" << endl;
	is >> s.units_sold;
	cout << "price:" << endl;
	is >> price;
	s.revenue = price * s.units_sold;
	return is;
}


ostream& print(ostream& os, const Sales_data& s)
{
	os << s.isbn() << " " << s.units_sold << " "
		<< s.revenue << " " << s.avg_price();
	return os;
}

Sales_data add(Sales_data& s1, Sales_data& s2)
{
	Sales_data sum = s1;
	s1.combine(s2);
	return sum;
}

int main()
{
	Sales_data total(cin);
	if (cin)
	{
		while (cin)
		{
			Sales_data trans(cin);
			if (total.isbn() == trans.isbn())
			{
				total.combine(trans);
			}
			else
			{
				print(cout, total) << endl;
				total = trans;
			}
		}
		print(cout, total) << endl;
	}
	else
	{
		cerr << "No data" << endl;
	}
	system("pause");
	return 0;
}
  • 练习7.22:
class Person
{
public:
	Person() = default;
	Person(string& name) :myName(name) {};
private:
	string myName;
	int myAge = 10;
};

可变数据成员

  • 将声明变量时添加mutable关键字后,即使在常量成员函数内也可以修改该变量
class Person
{
public:
	Person() = default;
	Person(string& name) :myName(name) {};
	void test() const
	{
		height = 1.5;
	}
private:
	mutable double height;
	string myName;
	int myAge = 10;
};

7.3.1节练习

alt

  • 练习7.23:
class Screen
{
public:
	typedef string::size_type pos;
	Screen() = default;
private:
	pos cursor = 0, height = 0, width = 0;
	string contens;
};
  • 练习7.24:
class Screen
{
public:
	typedef string::size_type pos;
	Screen() = default;
	Screen(pos h, pos w) :height(h), width(w), contens("  ") {};
	Screen(pos h, pos w, char s) : height(h), width(w), contens({s}) {};
private:
	pos cursor = 0, height = 0, width = 0;
	string contens;
};
  • 练习7.25:可以安全的依赖拷贝和赋值操作的默认版本,当前版本的Screen没有手动去开辟内存空间,所有的内存操作都是编译器和操作系统来进行的。
  • 练习7.26:在前面加上inline即可

对于公共代码使用私有函数功能

alt

alt

7.3.2节练习

alt

#include<iostream>
using namespace std;

class Screen
{
public:
	typedef string::size_type pos;
	Screen() = default;
	Screen(pos h, pos w) :height(h), width(w), contens(" ") {};
	Screen(pos h, pos w, char s) : height(h), width(w), contens(h* w, s) {};
	Screen& move(pos r, pos c)
	{
		pos row = r * this->width;
		this->cursor = row + c;
		return *this;
	};
	Screen& set(pos r, pos c, char ch)
	{
		this->contens[r * this->width + c] = ch;
		return *this;
	}
	Screen& set(char ch)
	{
		cout << this->cursor << endl;
		this->contens[this->cursor] = ch;
		return *this;
	}
	Screen& display(ostream& os)
	{
		do_display(os);
		return *this;
	}
	const Screen& display(ostream& os) const
	{
		do_display(os);
		return *this;
	}

private:
	pos cursor = 0, height = 0, width = 0;
	string contens;
	void do_display(ostream& os) const
	{
		os << this->contens;
	}
};

int main()
{
	Screen myScreen(5, 5, 'X');
	myScreen.move(4, 0).set('#').display(cout);
	cout << "\n";
	myScreen.display(cout);
	cout << "\n";
	system("pause");
	return 0;
}
  • 练习7.28~7.29:每次操作都是在对myScreen对象的副本进行操作,myScreen这个对象本身不会被改变
  • 练习7.30:略

类类型

  • 对于两个类来说,即使它们的成员完全一样这两个类也时不同的类型
Person s1
#include<iostream>
using namespace std;

class Person
{
	int a = 10;
};

class Dog
{
	int a = 10;
};

int main()
{
	Person p;
	Dog d = p; // 报错,无法将Person类型转化成Dog类型
	system("pause");
	return 0;
}

向前声明

  • 先声明后定义,在声明到定义这部分该类型属于向前声明的不完全类型。(我们知道它是一个类类型但是不知道它包含了哪些成员)
  • 对于不完全类型,我们可以定义指向这种类型的指针或者引用,可以将不完全类型作为形参或者返回类型的函数。
  • 指向自身的指针
class Link_screen {
  Link_screen *next;
  Link_screen *prev;
}

7.3.3节练习

alt

  • 练习7.31:
class Y;

class X
{
public:
	Y* ptrY;
};

class Y
{
public:
	X* ptrX;
};

再探友元

  • 友元函数可以定义在类内部,定义在类的内部是该友元函数是内联的
  • 可以将类声明成友元,友元类的成员函数可以访问该类所有成员(包括私有属性)
#include<iostream>
using namespace std;

class B;
class A
{
	friend class B;
private:
	int m_A = 10;
};

class B
{
public:
	void get_A()
	{
		A a;
		cout << a.m_A << endl;
		
	}
};

int main()
{
	B b;
	b.get_A();
	system("pause");
	return 0;
}
  • 友元不具有传递性(友元的友元不是我的友元)
  • 成员函数也可以做友元
#include<iostream>
using namespace std;

// 注意点1:需要先声明B类中需要做友元的函数
class B
{
public:
	void get_A();
};

// 注意点2:在A类中对B中的成员函数进行友元声明
class A
{
	friend void B::get_A();
private:
	int m_A = 10;
};

// 注意点3:在类外定义B类中做友元的函数
void B::get_A()
{
	A a;
	cout << a.m_A << endl;
}

int main()
{
	B b;
	b.get_A();
	system("pause");
	return 0;
}

  • 重载函数做友元,尽管重载函数的名字相同但重载函数如果需要做友元,需要针对每个重载函数都依次进行友元声明
  • 友元声明和作用域,友元声明仅仅是修改了访问权限并非普通意义山的声明(普通声明会影响作用域,友元声明不会),相当于友元声明后如果想使用该友元声明的函数,必须再对该函数进行一次普通声明

7.3.4节练习

alt

  • 练习7.32:
#include<iostream>
using namespace std;

class Screen;
class Window_mgr
{
public:
	void clear(Screen& s);
};

class Screen
{
	friend void Window_mgr::clear(Screen& s);
public:
	typedef string::size_type pos;
	Screen(pos h, pos w, char s) : height(h), width(w), contens(h* w, s) {};


private:
	pos cursor = 0, height = 0, width = 0;
	string contens;
};

void Window_mgr::clear(Screen& s)
{
	cout << s.contens << endl;
}

int main()
{
	Window_mgr w;
	Screen s(5, 5, 'X');
	w.clear(s);
	system("pause");
	return 0;
}

类的作用域

  • 定义在类外部的函数中的返回值是类中声明或定义的数据类型时,需要在这个数据类型前面也加上作用域(因为返回类型在作用域之外,所以也需要加上作用域)
class Screen;
class Window_mgr
{
public:
	using ScreenIndex = vector<Screen>::size_type;
	ScreenIndex addScreen(const Screen& s);
};

Window_mgr::ScreenIndex Window_mgr::addScreen(const Screen& s)
{
	return 1;
}

7.4节练习

alt

  • 练习7.33:
Screen::pos Screen::size() const
{ return height * width };

名字查找与类作用域

  • 名字查找:在名字所在块中寻找其声明语句,只查找该名字使用之前的块,如果没找到就查找外层作用域,最终还是没找到才会报错
  • 类中名字查找:先编译成员声明(此时函数体不可见),再编译函数体(此时在函数体内中类内的所有成员都被声明了所以都可见,如果此时函数体内查找不到才会去外层作用域查找)
typedef double Money;
string bal;
class Account{
public:
  Money balance() { return bal; }
private:
  Money bal;
};

上诉代码,balance函数的声明取自typedef double Money,函数体内的return bal取自Money bal

  • 类型名特殊处理:在来中使用了外层作用域的类型名后,不能在类内重新定义该类型名
  • 成员函数内的形参名与类的成员、全局作用域中的成员同名时会屏蔽掉成员名,此时可以通过类作用域和this指针来强制访问类成员名,通过全局作用域::来访问全局成员

7.4.1节练习

alt

  • 练习7.34:报错
  • 练习7.35:Type使用了类外定义的typedef string Type,initVal()使用了类内定义的initVal(),有错误:将返回值加上Exercise::即可

构造函数再探

  • 对于引用类型和const类型的成员或者是某种未提供默认构造函数的类类型需要使用显式地初始化
  • 初始化顺序与它们在类定义中的出现顺序一致,例子:(例子中的代码可以在visual 2022中正常执行不会报错,可能是visual 2022对编译进行了优化,但是结果正如我们猜测的一样,先定义i再定义j->使用未定义的j来初始化i导致i的值是是一个未定义的值)
#include<iostream>
using namespace std;
class X
{
	int i;
	int j;
public:
	// 先用j初始化i,由于j尚未未定义会报错,再用val初始化j,
	X(int val) : j(val), i(j)
	{
		cout << j << endl; // -> 1
		cout << i << endl; // -> 
	}
};
int main()
{
	X(1);
	system("pause");
	return 0;
}

alt 上诉代码中将j的声明调整值i的声明前即可修正错误

  • 如果一个构造函数为所有参数提供的了默认实参,则它实际上也定义了默认构造函数

7.5.1节练习

alt

  • 练习7.36:
struct X
{
	X(int i, int j) : base(i), rem(j) { };
	int base, rem;
};
  • 练习7.37:
Sales_data first_item(cin) // 使用Sales_data(std::istream &is)
Sales_data next 和 Sales_data last// 都使用Sales_data(std::string s = " ")

  • 练习7.38:
struct X
{
	X(istream& s = cin) {};
};
  • 练习7.39:不合法,这样会导致同时存在两个默认构造函数
  • 练习7.40:Employee
class Employee
{
	int EmployeeAge;
	string EmployeeName;
	string EmployeeResume;
public:
	Employee(int age, string name, string resume = " ") : EmployeeAge(age), EmployeeName(name), EmployeeResume(resume) {};

};

委托构造函数

  • 将函数的初始化过程通过初始值列表将构造职责委托给另外的构造函数(委托构造函数的成员初始值列表只有唯一一个入口就是类名本身,通过参数列表的不同委托不同的构造函数)
  • 委托构造中函数体的执行顺序是委托顺序逆向,先执行最终被委托的构造函数体,再依次执行
class Employee
{
	int EmployeeAge;
	string EmployeeName;
	string EmployeeResume;
public:
	Employee() :Employee(18) {}; // 委托给Employee(int age)
	Employee(int age) :Employee(age, " ") {}; // 委托给Employee(int age, string name, string resume = " ")
	Employee(int age, string name, string resume = " ") : EmployeeAge(age), EmployeeName(name), EmployeeResume(resume) {};

};

7.5.2节练习

alt

  • 练习7.41~7.42:
#include<iostream>
using namespace std;


class Employee
{
	int EmployeeAge;
	string EmployeeName;
	string EmployeeResume;
public:
	// 委托给Employee(int age)
	Employee() :Employee(18)
	{
		cout << "委托Employee(int age)" << endl;
	}
	// 委托给Employee(int age, string name, string resume = " ")
	Employee(int age) :Employee(age, " ")
	{
		cout << "委托给Employee(int age, string name, string resume = " ")" << endl;
	}
	Employee(int age, string name, string resume = " ") : EmployeeAge(age), EmployeeName(name), EmployeeResume(resume)
	{
		cout << "Employee(int age, string name, string resume = " ")执行初始化" << endl;
	}
};
int main()
{
	Employee();
	Employee(20);
	system("pause");
	return 0;
}

使用默认构造函数

alt

7.5.3节练习

alt

  • 练习7.43:
class NoDefault
{
public:
	NoDefault(int) {};
};

class C
{
	NoDefault my_mem;
public:
	C() = default;
};
  • 练习7.44:不合法,因为NoDefault没有合成默认构造函数无法进行默认构造
  • 练习7.45:不合法,因为C中包含了一个NoDefault类型的成员,无法为该成员提供默认构造函数
  • 练习7.46:
    (a)错误,即使不提供任何构造函数,编译器会为其合成默认构造函数
    (b)错误,为所有形参提供默认实参的构造函数也是默认构造函数
    (c)错误,必须为类提供默认构造函数(避免其他类定义中包含没有默认构造函数的类类型时而导致的报错)
    (d)错误,定义了构造函数或者类定义中包含了其他类类型(该类类型提供了构造函数),编译器不会为其提供合成默认构造函数

7.5.4节练习

alt

  • 练习7.47:应该是explicit,避免使用Sales_data时将不符合需求的数据作为初始化的值
  • 练习7.48:
    显式的将9-999-99999-9转化成字符串
    初始化 Sales_data item1
    初始化 Sales_data item2
    如果是explicit没差别
  • 练习7.49:
    (a)将s这个string对象隐式的转化成Sales_data对象
    (b)将s这个string对象隐式的转化成Sales_data&对象
    (c)会报错,常量成员函数无法修改对象数据
  • 练习7.50:略
  • 练习7.51:vector将其设置为单参数的构造函数在使用时,可能会因为程序错误导致额外的开销,如下:
#include<iostream>
#include<vector>
using namespace std;

void func(vector<int> vec) {};

int main()
{

	// 如果未被explicit修饰,那么根据隐式转化func(10)-->vector<int> vec(10),这段代码会创建10个元素的vector<int>对象,如果不小心传入1000...
	func(10); 	// 提示报错,是因为vector的单参数的构造函数被explicit

	system("pause");
	return 0;
}

聚合类

  • 聚合类:可以直接访问其成员。聚合类满足以下条件:所有成员都是public,没有定义构造函数,没有类内初始值,没有基类、虚函数。
  • 聚合类的初始值顺序必须和声明顺序一致 聚合类例子:
#include<iostream>
using namespace std;

struct Data
{
	int ival;
	string s;
};

int main()
{
	Data d1 { 20, "name" };
	system("pause");
	return 0;
}

7.5.5 节练习

alt 聚合类不能存在类内初始值,修改如下

struct Data
{
	std::string bookNo;
	unsigned units_sold;
	double revenue;
};

字面值常量类

  • 满足以下条件的是字面值常量类:数据成员都是字面值类型的聚合类是字面值常量类
  • 满足以下条件的也是字面值常量类:数据成员都是字面值类型,至少含有一个constexpr构造函数,constexpr构造函数必须初始化所有数据成员,有类内初始值的数据成员都是一条常量表达式,数据成员是类类型的都拥有该类类型的constexpr构造函数,必须使用默认析构函数

7.5.6节练习

alt

  • 练习7.53:略
  • 练习7.54:不应该被声明成constexpr,声明成constexpr代表该函数可以在编译阶段就可以计算出结果,但是被赋值的相关成员数据类型并不是constexpr类型而是普通成员变量,所以在编译期间无法确定它们的值,所以为这些set函数加上constexpr声明并不合适(但在visual 2022中可以运行)
  • 练习7.55:不是字面值常量类,string不是字面值类型

类的静态成员

  • 类的静态成员与类相关,类的所有对象成员对象同用一份静态数据
  • 无法在静态成员中使用this指针,静态成员函数也不能被声明成const
  • 静态成员只能定义一次,并且必须在类的外部定义、初始化每个静态成员,静态成员定义在任何函数之外,一直存在于程序的整个生命周期 声明及使用静态成员:
#include<iostream>
using namespace std;

class Account
{
public:
	void calculate() { amount += amount * interestRate; };
	// 定义静态成员函数
	static double rate() { return interestRate; };
	// 声明静态成员函数
	static void rate(double);
private:
	string owner;
	double amount;
	// 定义静态成员
	static double interestRate;
	// 声明静态成员函数
	static double initRate();
};


// 类外声明静态成员函数时不需要带上static,类内声明时带上static即可
void Account::rate(double newRate)
{
	interestRate = newRate;
}

double Account::initRate()
{
	return 0.01;
};

// 初始化静态成员
double Account::interestRate = initRate();


int main()
{
	double r;
	r = Account::rate();

	Account ac1;
	Account* ac2 = &ac1;
	r = ac1.rate();
	r = ac2->rate();

	system("pause");
	return 0;
}

静态成员的类内初始化

  • 通常情况下静态成员不应该在类内初始化,但当我们希望静态成员在运行阶段值不被改变,并且该静态成员仅对该类有重要且唯一的用途,那么我们可以在类的内部为该静态成员提供const int类型的类内初始值(其余的都不行),并且该静态成员是字面值常量类型的constexpr,最后再在外部定义它即可
class Account
{
	// 为静态成员提供类内初始值
	static constexpr int period = 30;
	static const int month = 12;
};

// 类内已经提供初始值,类外定义时就不能再提供初始值了,因为静态成员只允许被定义一次再提供初始值相当于重定义
constexpr int Account::period;
const int Account::month;

静态成员的特殊场景

  • 静态成员可以是不完全类型(数据成员则不行,指针或引用成员可以)
  • 静态成员可以作为默认实参(非静态数据成员则不能)

7.6节练习

alt

  • 练习7.56:
    静态成员:通过static修饰的类成员
    优点:所有对象共用一份静态数据为类对象的某些统一操作提供便捷
    区别:静态成员的生命周期与全局变量类似,定义后就一直存在直到程序结束时才会被销毁,静态成员只允许被定义一次,静态成员可以作为默认实参,静态成员可以是不完全类型。
  • 练习7.57:略
  • 练习7.58:
class Example
{
public:
	static double rate = 6.5;// 错误,无法为非const int类型的静态成员数据提供类内初始化,C++17中可以
	static const int vecSize = 20; // 正确
	static vector<double> vec(vecSize);// 错误,无法为非const int类型的静态成员数据提供类内初始化,C++17中可以使用内联变量
};
C++Prime学习笔记 文章被收录于专栏

勇敢和愚蠢只有一剑之差

全部评论
这些题都要做吗?太顶了吧
点赞 回复 分享
发布于 2023-03-31 16:13 黑龙江
你这内容是一本书里的嘛?能说下是什么书不
点赞 回复 分享
发布于 2023-04-01 09:09 河南

相关推荐

10-25 02:13
门头沟学院 C++
牛客7351937293号:8.27笔试10.22评估
投递小米集团等公司10个岗位
点赞 评论 收藏
分享
点赞 收藏 评论
分享
牛客网
牛客企业服务