设为首页 加入收藏

TOP

现代C++学习指南-具体类(三)
2023-07-23 13:26:33 】 浏览:91
Tags:现代 习指南
内大量创建并销毁对象,就会造成内存抖动,严重影响系统的稳定性,而且,我们的真正目的只是将两个变量的值交换一下而已。所以相较于拷贝,我们还有更好的选择:移动。

左值和右值

说起移动,就不得不提到左值和右值。这里的左和右是相对于=来说的。
我们知道=是用来赋值的,这下面隐藏着三个动作:生,取,写。在内存中生成一个临时数据,读取变量保存位置,将临时变量内容写入保存位置。生就是指的右值,它保存在我们不知道的内存位置,在写动作完成后,它就被回收了。而取对应的就是左值,我们用变量名保存了它的内存位置,在它作用域内可以反复读写。所以右值最大的特点就是不知道地址,如i=i+1就会先生成一个i+1的临时对象,我们不知道地址,所以它是右值。与之相对的左值,是可以通过&读到地址的。
接下来我们再来谈一谈引用。我们通常是用别名来理解引用的,但是可能会忽略一个小细节,别名也是需要有归属的,也就是它代表的地址在哪里。基于这个前提,我们就可以推导出凡是存在内存中的数据,理都是有地址的,而右值是存在内存中的,它也应该需要一种方式来获得地址,称之为右值引用,相对的一般变量的引用就称为左值引用。
说回到移动,前面的复制构造函数虽然能将数据和其他对象共享,但是大部分情况下,数据其实不需要共享的,只需要转移,也就是将数据的所有权移动到另一个对象上,原始对象就不再有效。所以C++提供了移动构造函数来完成这个操作。

class Sample {
private:
	int* value;

public:
	Sample(const int value) :value{ new int{value} } {
		std::cout << "Create value = " << value << std::endl;
	}

	Sample(const Sample& sample) :value{ new int {*sample.value} } {
		std::cout << "Copy create object" << std::endl;
	}

	Sample(Sample&& sample) :value{ sample.value } {
		sample.value = nullptr;
		std::cout << "Move create object" << std::endl;
	}

	~Sample() {
		delete value;
		std::cout << "destory sample" << std::endl;
	}

	friend std::ostream& operator<<(std::ostream& os, const Sample& sample) {
		os << "Sample value is " << sample.value;
		return os;
	}

};

void use(Sample sample) {
	std::cout << "Use sample " << sample << std::endl;
}


int main() {
	// 普通变量,1被使用后马上销毁了
	int a = 1;
	//左值引用
	int& b = a;
	//右值引用,引用的就是1那个暂存的地址
	int&& c = 1;
	//可以修改引用的值
	c = 2;

	Sample sample{ 1 };

	use(std::move(sample));

	std::cout << sample << std::endl;
}
// 输出
// Create value = 1
// Move create object
// Use sample Sample value is 009B8E90
// destory sample
// Sample value is 00000000
// destory sample

在上面的代码里,我们真正使用sample对象的是函数useuse执行完后,sample就没用了。所以我们用std::move将数据转移到了函数实参中,外部的sample不再拥有那块内存的占用。很多场景其实都是类似的情况:外部配置参数后,传递给某个函数使用,所以这种情况下就没必要构造一个新的对象出来,假如业务很长的话,sample对象就会一直占用内存,但是它是早就没用了的。所以移动构造函数就发挥了大作用。

数据共享

除了通过复制构造函数和成员函数共享数据外,还可以通过友元类和友元函数。它们都是一种特殊的访问数据的形式,可以直接访问到数据,不经过成员函数的调用。所以在有些时候友元能帮助减少函数调用的花销,有些时候则会引入不可预期的行为。

class FriendClass {

public:
    void useSample(const Sample& sample) {
        std::cout << "Sample value is " << sample.value << std::endl;
    }
};

上面的例子,如果按照常规是无法通过编译的,因为samplevalue是私有的。前面我们知道,成员函数是可以访问私有变量的,但是这个类是定义在Sample外的,这个函数是另一个类的成员函数,完全没办法完成这种访问。当然,这种情况下,我们可以修改Sample类的定义,添加一个成员函数就解决了。但是假如FriendClass有多个成员函数都需要访问Sample的私有成员呢,这个时候添加成员函数的方式就不再适用,所以出现了友元类。
实现友元类很简单,简单到只需要添加一条声明。首先友元类需要至少两个类,一个类是想要访问私有成员的源类,另一个是含有私有成员的目标类,然后我们把友元声明放在目标类里,源类就可以顺利访问到目标类的私有成员了。在上面的例子FriendClass想要访问Sample的私有成员,所以它是源类,是普普通通的类。Sample含有FeiendClass想访问的私有成员value,所以它是目标类,声明需要添加到它的类定义里面。

Class Sample{
    private:
    int value;
    friend class FriendClass;
    
    //其余不变
}

加上这一条之后,前面的FriendClass就可以正常通过编译了。这一句的威力很大,大到FriendClass的所有成员函数都能访问到value。假如这不是你的期望,但是还是想要直接访问到value,那么就可以适用友元函数。
友元函数是普通的函数,虽然它声明在类里,但是不能直接访问到类的私有成员,而是通过函数参数的形式。为了和普通的成员函数区分开来,它的声明最前面需要添加关键字friendfriend仿佛像打开了权限控制的开关,可以使函数访问到参数的私有成员。

class Sample{
    friend std::ostream& operator<<(std::ostr
首页 上一页 1 2 3 4 下一页 尾页 3/4/4
】【打印繁体】【投稿】【收藏】 【推荐】【举报】【评论】 【关闭】 【返回顶部
上一篇C++异常处理机制 下一篇MFC中使用多线程

最新文章

热门文章

Hot 文章

Python

C 语言

C++基础

大数据基础

linux编程基础

C/C++面试题目