C++的面向对象学习(5):对象的重要特性:对象的成员变量和成员函数深入研究

2023-12-22 16:33:00

提示:文章写完后,目录可以自动生成,如何生成可参考右边的帮助文档

文章目录

一、static修饰的静态成员:与类本身关联,不依赖于任何对象。

①静态成员变量:

所有实例化的对象都共享同一份数据。
在编译阶段就会分配内存,而不是实例化对象后才分配。
静态成员变量一般在类里面声明,而在类外进行赋值初始化。
静态成员变量可以通过类名+作用域解析运算符 :: 来访问,而不需要创建类的对象。

②静态成员函数:

所有实例化的对象共享同一个函数。
静态成员函数是与类本身相关联的函数,它们不依赖于类的任何对象。
静态成员函数可以通过类名+作用域解析运算符 :: 来调用,而不需要创建类的对象。
静态成员函数只能访问静态成员变量和其他静态成员函数,不能直接访问非静态成员变量和非静态成员函数。

这里主要是注意以下这些问题:

(1)类内声明静态成员变量,类外赋值初始化静态成员变量。

class Person {
public:
	int age;
	string name;

	static int height;//身高
	
	int getHeight() {
		return height;
	}
};

我们声明了一个公开的静态成员变量height,就需要在类外面对其赋值初始化。

int Person::height = 0;//在类外部先赋值初始化

(2)要操作和访问这个静态成员变量,既可以通过常规的实例化对象去访问,也可以通过类名+::作用域去访问。两个不同的实例化对象,共享的是同一个静态成员变量。

int main() {
	Person p(18, "小明", "华为mate60", 5999);
	Person p2(19, "小李", "华为mate60pro", 7999);
	
	Person::height = 150;//第一次修改身高
	p.height = 160;//第二次修改
	p2.height = 180;//第三次修改
	cout << Person::height << endl;
	system("pause");
	return 0;
}

(3)关于访问权限:公开的静态成员变量可以在主函数里访问操作,那么私有的呢?

通过提供一个公有的静态成员函数来间接初始化私有静态成员变量。这个静态成员函数可以在类的内部访问和修改私有静态成员变量,并在需要的时候进行初始化。

class Person {
private:
    static int weight;

public:
    static int height;
    
    void setWeight(int a) {
        weight = a;
    }

    int getWeight() {
        return weight;
    }
};

int Person::height = 0;
int Person::weight = 0;

(4)静态的成员函数只能访问静态成员变量,不能访问其他成员变量。

在这里插入图片描述
age这是一个普通变量,就不能被静态函数访问。
原因是:我们可能会将一个类实例化很多个对象,静态成员变量被共享,所以编译器觉得你可以通过静态函数随便修改。但是,每个对象的普通成员变量是不共享的,不能通过一个公用的静态函数去修改,编译器会分辨不清你到底要修改的是哪个对象的成员变量

(5)静态成员变量和静态成员函数的使用场景:

1.静态成员变量适用于表示与类相关的全局信息,例如记录类的实例个数、保存全局配置等。
2.静态成员函数适用于不依赖于类的任何对象,但仍然与类相关的操作,例如提供工具函数、计算与类相关的统计信息等。

二、类的成员变量和成员函数分开存储

非静态成员变量存储在每个类的对象中,每个对象都有自己的成员变量副本。
非静态成员函数存储在类的代码段中,它们不占用对象的内存空间。

问题①:实例化一个对象,他到底占用多大的内存空间呢?

(1)一个空类的内存多大?—— 一个字节

	Car c1;
	cout << sizeof(c1) << endl;

答案:一个字节。一个空类至少会占用一个字节的内存空间,以确保每个对象在内存中有独一无二的地址。这是因为每个对象在内存中必须具有唯一的地址,以便能够区分不同的对象

(2)类中有一个整形变量,内存多大?——四个字节

答案:把类看作是一个结构体,那么其实这时实例化对象后,这个对象的大小就是4字节。

(3)类中有一个整形和一个静态整形变量,内存多大?——四个字节

答:因为静态变量不属于对象的属性,它只与类关联,所以不属于类的对象,所以实例化的对象的大小只是那个普通的整型变量speed的大小。

class Car {
private:
	int speed;
	static int colour;
};
int main() {
	Car c1;
	cout << sizeof(c1) << endl;
	return 0;
}

(4)类中有一个整形、一个静态整形、一个非静态成员函数,内存多大?——四个字节

答:非静态成员函数同样不属于类的对象,它存储在类的代码段。当调用成员函数时,会将对象的地址作为隐含参数传递给函数,以便函数可以访问对象的成员变量。

class Car {
private:
	int speed;
	static int colour;

public:
	void func() {
		cout << "666" << endl;
	}
};

(5)类中有一个整形、一个静态整形、一个非静态成员函数、一个静态成员函数,内存多大?——四个字节

答:静态成员函数同样不属于类的对象

class Car {
private:
	int speed;
	static int colour;

public:
	void func() {
		cout << "666" << endl;
	}
	static void add() {
		cout << "777" << endl;
	}
};

类实例化一个对象后,只有类的非静态成员变量属于该对象,占据该对象的内存空间。其他的变量和函数均不属于该对象。

总结:一个对象的内存空间主要由其非静态成员变量决定,而其他的静态成员变量和成员函数不占据对象的内存空间。注意一个空类的对象占1个字节。

三、this指针:成员函数存储在代码段中,但在调用时会传递一个指向当前对象的指针(this指针)作为隐含参数。

1.问题的提出:既然成员函数不属于对象,那怎么区分哪个对象调用的自己呢?

2.回答:每个对象都可以调用类的成员函数,但是在成员函数内部,可以通过特殊的指针 this 来区分是哪个对象调用了自己。

3.this指针的特点

①this指针指向当前被调用的成员函数所属的对象。比如目前是p1在调用函数,this指针就指向p1,然后p22调用这个函数,this指针就指向p2。
②this指针是隐含在成员函数内部的,不需要定义,直接使用。

4.this指针的用途

①解决函数传参与成员变量名相同导致冲突的问题。

在这里插入图片描述
这个样子,编译器就认为三个speed都是一个东西。
用this指针修改:

	void func(int speed) {
		//speed = speed;
		this->speed = speed;
	}

int main() {
	Car c1;
	c1.func(180);
	system("pause");
	return 0;
}

主程序调用c1的func函数时,this指针指向的就是当前的对象c1,于是就有类似结构体指针的操作,用->运算符代表对象c1内部的具体成员变量
在这里插入图片描述

②成员函数返回对象本身,使用 return *this语句,从而支持链式调用

#include <iostream>

class MyClass {
private:
    int value;

public:
    MyClass(int val) : value(val) {}

    MyClass& setValue(int val) {//返回一个指向 MyClass 类型对象的引用
        value = val;
        return *this;
    }

    MyClass& add(int num) {
        value += num;
        return *this;
    }

    void printValue() {
        cout << "Value: " << value << endl;
    }
};

int main() {
    MyClass obj(5);
    obj.setValue(10).add(3).printValue();  // 链式调用

    return 0;
}

MyClass 类有三个成员函数:setValue()、add() 和 printValue()。setValue() 函数用于设置对象的值,add() 函数用于将给定的数值添加到对象的值上,printValue() 函数用于打印对象的值。
通过返回引用,我们可以在链式调用中持续操作同一个对象,而不是每次调用都返回一个新的对象副本。

四、const修饰的常成员:

①常成员变量:声明后其值无法被修改

常成员变量必须在类的构造函数中进行初始化,一旦被初始化,就不能再修改它们的值。
常成员变量的值在整个对象的生命周期中都保持不变。

②常函数:用const修饰成员函数

1.常函数内部不可以修改成员属性。
2.若加关键字mutable来修饰成员属性,则在常函数中依然可以修改。
在这里插入图片描述

③常对象:用const修饰对象

mutable int colour;//声明其是可变变量

const Car c3;
c3.setColor(5);

需要在常成员函数中修改成员变量的值,可以将成员变量声明为 mutable,这样就可以在常成员函数中修改它们的值了。
需要注意的是,mutable 成员变量可以在常成员函数中被修改,但不能在常量对象上调用非常量成员函数来修改它们的值

即:常对象不能调用普通成员函数,只能调用常函数。 因为普通成员函数可以修改属性。

五、友元:允许一个类或函数访问另一个类的私有成员

什么叫友元?
对于一个类,私有权限的属性就好比卧室,公开权限的属性就好比客厅。

如果我想让类外面的一些特殊的函数或者类可以访问这些私有权限的属性,就需要友元。
关键字:friend

1.一个全局函数做一个类的友元

非常简单。让一个全局函数成为类的友元函数,可以在类的定义中使用 friend 关键字来声明该函数。这样,该全局函数就可以访问类的私有成员。
注意,在类中声明友元函数,随便放,不用管它是私有还是公开。

class HOUSE {

	friend void goodbey(HOUSE hou);//最重要,只需要声明这一句即可。

public:
	string sittingRoom;//客厅

	HOUSE(string a, string b) :sittingRoom(a), bedroom(b) {}
private:
	string bedroom;//卧室

};

void goodbey(HOUSE hou) {

	cout << hou.sittingRoom << endl;//公开成员变量,无论何时全局函数都能访问
	cout << hou.bedroom << endl;//私有成员变量,要在类里面将全局函数声明为友好的,才能访问
}

2.一个类做另一个类的友元

HOUSE 类中声明了一个名为 Visitor 的类作为友元。这意味着 Visitor 类可以访问 HOUSE 类的私有成员 bedroom。
语法也很简单:和全局函数类似,在类中用关键字friend声明那个需要做友元的类即可。

class HOUSE {

	friend void goodbey(HOUSE hou);
	friend class Visitor;
public:
	string sittingRoom;//客厅

	HOUSE(string a, string b) :sittingRoom(a), bedroom(b) {}
private:
	string bedroom;//卧室

};

class Visitor {//一个类作为友元
public:
	void visit(HOUSE& hou) {
		hou.bedroom = "666";
		cout << hou.bedroom << endl;
	}
};

3.一个类的成员函数做另一个类的友元

一个类的成员函数可以被声明为另一个类的友元函数。这意味着该成员函数可以访问友元类的私有成员。
这里理论上代码是这样的:

class HOUSE;

class Watcher {
public:
    void look(HOUSE& hou);
};

class HOUSE {
    friend void goodbey(HOUSE hou);
    friend class Visitor;
    friend void Watcher::look(HOUSE& hou);

public:
    string sittingRoom; // 客厅

    HOUSE(string a, string b) : sittingRoom(a), bedroom(b) {}

private:
    string bedroom; // 卧室
};

void Watcher::look(HOUSE& hou) {
    hou.bedroom = "777";
}

void goodbey(HOUSE hou) {
    cout << hou.sittingRoom << endl;
    cout << hou.bedroom << endl;
}

class Visitor {
public:
    void visit(HOUSE& hou) {
        hou.bedroom = "666";
        cout << hou.bedroom << endl;
    }
};

但是我用VS2022编译会报错,不管怎么修改类的声明与定义的前后顺序都会报错,不知道为什么。这个问题以后遇到再说,反正,目前用前两种方法就行。

4.总结:目前如果要访问类的私有成员变量,有哪些方法?

如果要访问类的私有成员变量,有以下几种方法:

使用公有成员函数:在类中定义公有成员函数,通过这些函数来访问和修改私有成员变量。这种方法可以提供对私有成员变量的控制和封装。

使用友元函数:在类中声明友元函数,使其能够访问类的私有成员变量。友元函数可以是全局函数,也可以是其他类的成员函数。友元函数可以直接访问类的私有成员变量,但会破坏封装性。

使用友元类:在类中声明友元类,使其能够访问类的私有成员变量。友元类的所有成员函数都可以访问类的私有成员变量。友元类可以提供更多的灵活性和控制,但同样会破坏封装性。

需要注意的是,尽量遵循封装的原则,只在必要的情况下使用友元函数或友元类来访问私有成员变量。尽量通过公有接口(公有成员函数)来操作私有成员变量,以保持类的封装性和安全性。

文章来源:https://blog.csdn.net/qq_53092944/article/details/135107706
本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。