复习C++小结(四)——类

const成员函数和可变数据成员mutable

我们知道,this指针的默认类型是指向类类型非常常量版本的指针常量。这样就会带来一个问题,我们不能够把this指针绑定当一个常量对象上(因为底层const等级不同)。比如下面这个例子。

class Solution
{
public:
	string isbn;
	string getIsbn() { return isbn; };
};


int main()
{
	const Solution s;
	s.getIsbn(); // 非法,因为this指针不能够指向const Solution类型对象
}

此时,我们可以用const关键字将getIsbn()声明为常量成员函数,其本质是将this指针声明为一个指向常量对象的指针常量。

	string getIsbn() const { return isbn; }; // 注意const的位置

此时再调用getIsbn()就合法,因为this指针的类型是const Solution *const,它可以指向类型为const Solution的对象。不过,由于this指针是指向常量的指针,所以在const成员函数内不能改变调用它的对象的内容。如果非要修改的话,对应的数据成员应该用mutable(可变)修饰。例如:

class Solution
{
public:
	mutable string isbn;
	string getIsbn() const { isbn = "1"; return isbn; }; // 合法
};

此外,构造函数不能够被声明成const的。因为当我们创建类的一个const对象时,直到构造函数完成初始化的过程,对象才真正取得const属性。因此,构造函数在const对象构造过程中可以对其写值。

类型成员

和普通成员类似,我们也可以自定义某种类型在类中的别名,这就是类型成员。它同样也有访问限制。

class Solution
{
public:
	using pos = int;
	pos size(string str) { return str.size(); };
};
int main()
{
	Solution::pos i; // pos是public的,因此可以被外部访问。
}

和普通成员不同的是,类型成员必须先定义后使用。

返回*this的成员函数

看下面这个类,其中move和set都是返回值类型是Screen&且返回*this的成员函数,这意味着他们的返回值是调用对象的引用,从而可以对同一对象s1进行连续操作,所以程序最后输出的结果是3,4。

class Screen
{
public:
	friend ostream &print(ostream &cout, Screen &s);
	Screen &move(int dx, int dy) { cor_x += dx; cor_y += dy; return *this; };
	Screen &set(int x, int y) { cor_x = x; cor_y = y; return *this; };
private:
	int cor_x = 0;
	int cor_y = 0;
};

ostream &print(ostream &cout, Screen &s)
{
	cout << s.cor_x << "," << s.cor_y;
	return cout;
}

int main()
{
	Screen s1;
	s1.set(2, 3).move(1, 1);
	print(cout, s1); // 输出3,4
}

如果将move和set的返回值改为Screen类型,结果就会是2,3,因为这相当于在调用完set之后创建了一个新的对象,在对这个新的对象进行move操作,s1本身的值只会被set函数改变一次。

// 将move和set返回值改为Screen类型,其他不变。
int main()
{
	Screen s1;
	s1.set(2, 3).move(1, 1);
	// 等价于Screen temp = s1.set(2,3);temp.move(1,1);
	print(cout, s1); // 输出2,3
}

用于类成员声明的名字查找

编译器处理完类中全部声明后才会处理成员函数的定义。 要理解这一点可以看下面这个例子:

typedef double Money;
string bal;
class Account
{
public:
	Money balance() { return bal; };
private:
	Money bal;
};

balance的函数体在整个类可见后才会被处理,因此该函数的return语句返回名为bal的成员,而非外城作用域的string对象。

但类型成员要特殊处理,看下面这个例子。

typedef double Money;
string bal;
class Account
{
public:
	Money balance() { return bal; };
private:
	typedef int Money;
	Money bal;
};

由于内层作用域可以重新定义外层作用域的名字,因此代码是合法的。但需要注意的是编译器只会在首次出现该别名之前寻找别名的定义。比如代码中,Money balance()的Money是外层定义的Money,实则double类型,但是Money bal的Money是类内定义的int类型。所以我们应当避免这种写法,并且保证类型成员放在类的最前面。

委托构造函数

C++11中新增了委托构造函数。一个委托构造函数使用它所属类的其他构造函数执行它自己的初始化过程,或者说它把自己的一些(或者全部)职责委托给了其他构造函数。委托构造函数的初始值列表只有一个,就是其他的构造函数。
举个例子,下面代码输出construct func1 construct func2。首先data在定义时应该调用无参数的构造函数,即输出func2的函数,随后由于该构造函数是委托构造函数,它将初始化的一部分委托给输出func1的构造函数,因而先调用Sales_data(const string &s, int a),使用初始化列表初始化isbn和cnt,随后执行函数体输出construct func1 ,之后控制权回到委托构造函数的函数体,输出construct func2。

class Sales_data
{
public:
	Sales_data(const string &s, int a) :isbn(s), cnt(a) { cout << "construct func1 "; };
	Sales_data() : Sales_data("", 0) { cout << "construct func2 "; }; // 委托构造函数

private:
	string isbn;
	int cnt;
};

int main()
{
	Sales_data data;
}

隐式的类类型转换

如果构造函数只接受一个实参,则它实际上定义了转换为此类类型的隐式转换机制,有时我们把这种构造函数称为转换构造函数
同样是上面的例子,经过了一点小修改,代码中Sales_data(const string &s)由于只接受一个实参,所以它是一个转换构造函数。因此Sales_data i = s合法,编译器会用string类型对象s来创建一个Sales_data类型的对象,并把它赋值给i。
但是,两步转换是不允许的,比如Sales_data i = “123” 这一句中"123"是const char *类型的,首先他要转换成string,之后才能由转换构造函数初始化一个Sales_data对象,这是不允许的。

class Sales_data
{
public:
	Sales_data(const string &s) :isbn(s) {};
private:
	string isbn;
	int cnt = 0;
};

int main()
{
	string s = "123";
	Sales_data i = s;     // 合法
	Sales_data i = "123"; // 非法,不能两步转换
}

此外可以用explicit关键字来禁止这种转换操作。如将上述代码中的构造函数改为:

explicit Sales_data(const string &s) :isbn(s) {};

此时Sales_data i = s就是非法的。同时explicit修饰的构造函数只能直接初始化,即Sales_data i(s)合法。

发布了6 篇原创文章 · 获赞 0 · 访问量 88

猜你喜欢

转载自blog.csdn.net/qq_33584870/article/details/104696004