类的作用域和类成员的访问(句柄)
成员运算符( . ) 作用域运算符(:: )
全局域 Global Scope
- 定义:在 所有函数和类之外 定义的变量、函数或对象
- 生命周期:整个程序运行期间存在(存储在静态存储区)
- 访问权限:可以被 同一文件 的所有函数访问。通过
extern
声明,可被 其他文件 访问(全局变量跨文件共享)
局部域(Local Scope)
- 定义:在 函数、代码块(
{}
)或类成员函数内 定义的变量或对象。 - 生命周期:从定义点开始,到所在块结束时销毁(存储在栈上)
- 访问权限 – 隐藏机制:仅在定义它的块内可见(如函数内部、
if
/for
块等)。同名局部变量会 隐藏(shadow) 外层变量(如全局变量)。如果还需要在内部使用被隐藏的同名全局变量的数据成员,可以通过在其名前加类名作用域运算符(:: ),全局变量前加作用域运算符来访问
int val = 100;
void badPractice() {
int val = 50; // 遮蔽全局变量
std::cout << val; // 输出: 50(局部变量)
std::cout << ::val; // 输出: 100(全局变量)
}
在类的作用域内,类的成员(数据和函数)可以被类的所有成员函数直接访问;
在类的作用域之外,,public类成员通过对象的句柄(handle)之一:对象,对象的引用,对象的指针来访问。
对象和对象的引用通过对象名 .成员名访问成员,对象的指针通过对象名->成员名访问成员、(每次通过对象的句柄调用成员函数时,编译器会插入一个隐式的this指针)。本质是 简化这个“解引用+访问”的过程的语法糖(syntactic sugar)ptr->member等价于 (*ptr).member
- 为什么
->
不能用于对象:因为->
是为指针设计的语法糖,对象没有指针的解引用步骤。如果强行用obj->member
,编译器会报错(除非类重载了->
运算符,如智能指针)。 - 为什么引用为什么用
.
引用是对象的别名,语法上完全等同于对象本身,因此用.
struct
和class
的成员访问规则完全一致
class Student {
public:
int age;
void study() { cout << "Studying..." << endl; }
};
int main() {
Student s; // 栈上的对象
s.age = 20; // 用 . 访问成员变量
s.study(); // 用 . 调用成员函数
Student& ref = s; // 引用
ref.age = 21; // 用 . 访问引用对象的成员
Student* p = new Student(); // 堆上的对象(指针)
p->age = 20; // 用 -> 访问成员变量
p->study(); // 用 -> 调用成员函数
//如果非要用 . 操作指针,可以先用 * 解引用指针,再使用 .,但代码会冗余,不推荐:
Student* p = new Student();
(*p).age = 20; // 等价于 p->age = 20;
(*p).study(); // 等价于 p->study();
delete p; // 释放内存
return 0;
}
如果成员是私有权限(private)或者被保护权限(protected)无法被访问,但存在两种情况私有权限(或被保护权限)的成员也可以访问:友元函数或友元类里
class MyClass {
private:
int secret = 42; // 私有成员
// 声明友元函数
friend void friendFunction(MyClass& obj);
// 声明友元类
friend class FriendClass;
};
// 友元函数定义
void friendFunction(MyClass& obj) {
cout << "友元函数访问私有成员: " << obj.secret << endl; // 合法
}
// 友元类定义
class FriendClass {
public:
void showSecret(MyClass& obj) {
cout << "友元类访问私有成员: " << obj.secret << endl; // 合法
}
};
类成员函数定义的本类对象
class Student {
private:
int age; // 私有成员
public:
void setAge(int a) { age = a; }
// 成员函数访问其他同类对象的私有成员
void compareAge(const Student& other) {
if (this->age > other.age) { // 直接访问 other.age(合法)
cout << "我比对方年长" << endl;
} else {
cout << "我比对方年轻" << endl;
}
}
};
类作为函数参数
函数参数类型如果是类,和基本数据类型一样,也有三种情况:值传递(类对象)、地址传递(对象指针)、引用传递(对象引用)。此外const也可修饰对象,所以共有6种函数参数的情况
- 类对象:void func(T t);
- 类对象:void func(const T t);
- 类对象指针:void func(T* pt);
- 类对象指针:void func(const T* pt);
- 类对象引用:void func(T&rt);
- 类对象引用:void func(const T& rt);
1和2实际传递进入函数的是实参的副本(调用拷贝构造函数),所以这两种情况在函数里不可能修改实参,且2对副本也不可能修改;
3和4用类对象指针作为参数,虽然对象指针传递进入函数也是这个对象指针的副本,但因为传递进入函数的是实参的地址,通过对象地址是能够修改实参对象的值;
4因为const修饰后是禁止通过地址修改实参的,所以4的效果是和1相似。而实际应用中通常更多使用1,因为简洁且安全;
5和6用类对象引用作为参数,实际传递进入函数的就是实参本身,所以是能够直接修改对象的值。
其中5的效果和3相似,可以修改实参,但前面2.5节提过,使用引用要优于指针。
其中6因为const修饰后是禁止修改实参的,所以6的效果是和1相似。而实际应用中通常使用6更优,因为效率更优。