const
成员是指在类中声明的成员(变量或函数),其值或行为在对象的整个生命周期中都是不可修改的。这有助于确保对象的某些属性或行为不会被意外地更改,从而提高代码的可靠性和可维护性。
C++中有两种使用 const
的情况:const
成员变量和const
成员函数。
const 成员变量:
这是类中被声明为 const
的成员变量。一旦对象被创建,这些变量的值就不能被修改。const
成员变量必须在类的构造函数的成员初始化列表中进行初始化。
class MyClass {
public:
MyClass(int value)
: myConstValue(value) {}
int getValue() const {
return myConstValue;
}
private:
const int myConstValue;
};
const 成员函数:
const
成员函数承诺不会修改对象的任何非 mutable
成员变量,并且可以在const
对象上调用。这允许在const
对象上执行只读操作。const
修饰类成员函数,实际修饰该成员函数隐含的this
指针,表明在该成员函数中不能对类的任何成员进行修改。
class MyClass {
public:
int getValue() const { return myValue; } // 不修改成员变量
void setValue(int value)const { myValue = value; } // err 不能修改成员变量
private:
int myValue;
};
需要注意的是,mutable
关键字可以用于修饰成员变量,即使在 const
成员函数内,这些被标记为 mutable
的变量仍然可以被修改。
class MyClass {
public:
int getValue() const { return myValue; } // 不修改成员变量
void setValue(int value) const { myValue = value; } //即使是const成员函数,也可以修改mutable成员变量
private:
mutable int myValue;
};
问题:
- const对象可以调用非const成员函数吗? 答:不可以。因为const对象的成员函数是const的,而非const成员函数是不const的,两者不匹配。如果强制调用,会导致编译错误。
- 非const对象可以调用const成员函数吗? 答:可以。因为非const对象的成员函数既可以是const的,也可以是非const的,两者匹配。
- const成员函数内可以调用其他的非const成员函数吗? 答:不可以。因为const成员函数不能修改对象的状态,而非const成员函数可能会修改对象的状态,这样会导致编译错误。
- 非cosnt成员函数内可以调用其他的cosnt成员函数吗? 答:可以。因为非cosnt成员函数既可以是cosnt的,也可以是非cosnt的,两者匹配。
静态成员是类的成员,它们与类本身相关,而不是与类的实例(对象)相关。静态成员在所有类的实例之间共享,并且可以通过类名访问,而不需要创建类的实例。C++中有两种类型的静态成员:静态数据成员和静态成员函数。
这是类的成员变量,它在类的所有实例之间共享。静态数据成员在类的声明中被声明为static
,但是它们需要在类外部进行定义和初始化。通常,这是为了确保类的所有实例共享同一个数据。
class MyClass {
private:
static int staticVar; // 静态数据成员的声明
};
int MyClass::staticVar = 0; // 静态数据成员的定义和初始化
int main() {
MyClass obj1;
MyClass obj2;
obj1.staticVar = 5;
std::cout << obj2.staticVar; // 输出为 5,因为静态数据成员在所有实例之间共享
return 0;
}
注意: 这里静态成员变量staticVar虽然是私有,但是我们在类外突破类域直接对其进行了访问。这是一个特例,不受访问限定符的限制,否则就没办法对静态成员变量进行定义和初始化了。
静态成员函数与类相关联,但不操作类的实例数据,因此它们没有隐式的this
指针。静态成员函数可以通过类名直接调用,无需创建类的对象。静态成员函数没有隐藏的this指针,不能访问任何非静态成员。通常,它们用于执行与类相关的操作,不需要访问实例特定的数据。
class Test {
public:
static void Fun() {
cout << _a << endl;//error不能访问非静态成员
cout << _n << endl;//correct
}
private:
int _a; //非静态成员
static int _n;//静态成员
};
小贴士:含有静态成员变量的类,一般含有一个静态成员函数,用于访问静态成员变量。(普通成员函数也可以访问静态成员)
当静态成员变量为公有时,有以下几种访问方式:
class Test {
public:
static int _n;//公有
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main() {
Test test;
cout << test._n << endl; //1.通过类对象突破类域进行访问
cout << Test()._n << endl;//3.通过匿名对象突破类域进行访问
cout << Test::_n << endl; //2.通过类名突破类域进行访问
return 0;
}
当静态成员变量为私有时,有以下几种访问方式:
class Test {
public:
static int GetN() {
return _n;
}
private:
static int _n;
};
// 静态成员变量的定义初始化
int Test::_n = 0;
int main() {
Test test;
cout << test.GetN() << endl; //1.通过对象调用成员函数进行访问
cout << Test().GetN() << endl;//2.通过匿名对象调用成员函数进行访问
cout << Test::GetN() << endl; //3.通过类名调用静态成员函数进行访问
return 0;
}
静态成员和类的普通成员一样,也有public、private和protected这三种访问级别,所以当静态成员变量设置为private时,尽管我们突破了类域,也不能对其进行访问。
总结:
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
- 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
- 类静态成员即可用 类名::静态成员 或者 对象.静态成员来访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public、protected、private 访问限定符的限制
注意区分两个问题:
1、静态成员函数可以调用非静态成员函数吗?
2、非静态成员函数可以调用静态成员函数吗?
问题1:不可以。因为非静态成员函数的第一个形参默认为this指针,而静态成员函数中没有this指针,故静态成员函数不可调用非静态成员函数。
问题2:可以。因为静态成员函数和非静态成员函数都在类中,在类中不受访问限定符的限制。
友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以 友元不宜多用。
友元分为:友元函数和友元类
友元函数是一种特殊类型的函数,它被允许访问类的私有成员,尽管这些函数不是类的成员。友元函数通常在类的声明中通过friend
关键字进行声明,并且可以访问声明它为友元的类的私有和受保护成员。这种机制提供了对封装性的一定程度的破坏,但有时候它是必要的,例如用于实现操作符重载或者在某些特殊情况下的优化。
示例:
class Date {
// 友元函数的声明
friend ostream &operator<<(ostream &out, const Date &d);
friend istream &operator>>(istream &in, Date &d);
public:
Date(int year = 0, int month = 1, int day = 1) {
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
// <<运算符重载
ostream &operator<<(ostream &out, const Date &d) {
out << d._year << "-" << d._month << "-" << d._day << endl;
return out;
}
// >>运算符重载
istream &operator>>(istream &in, Date &d) {
in >> d._year >> d._month >> d._day;
return in;
}
注意: 其中cout是ostream类的一个全局对象,cin是istream类的一个全局变量,<<和>>运算符的重载函数具有返回值是为了实现连续的输入和输出操作。
说明:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用const修饰
- 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
友元类是指一个类可以将另一个类声明为它的友元,从而允许友元类访问声明它为友元的类的私有和受保护成员。这在一些特殊情况下可能很有用,但需要慎重使用,因为它可能破坏类的封装性。
示例:
class FriendClass {
public:
void display(const class MyClass& obj);
};
class MyClass {
private:
int privateData;
public:
MyClass(int data) : privateData(data) {}
// 声明 FriendClass 为友元类
friend class FriendClass;
};
// 在 FriendClass 中可以访问 MyClass 的私有成员
void FriendClass::display(const MyClass& obj) {
std::cout << "FriendClass accessing private data: " << obj.privateData << std::endl;
}
int main() {
MyClass obj(42);
FriendClass friendObj;
friendObj.display(obj);
return 0;
}
友元类说明:
1、友元关系是单向的,不具有交换性。
例如上述代码中,B是A的友元,所以在B类中可以直接访问A类的私有成员变量,但是在A类中不能访问B类中的私有成员变量。
2、友元关系不能传递。
如果A是B的友元,B是C的友元,不能推出A是C的友元。3、友元关系不具有继承性,即如果类A是类B的友元,类B的子类不会自动成为类A的友元。
内部类是一个类定义在另一个类的内部的类。也就是说,一个类可以在另一个类的内部声明和定义一个类,被声明的类被称为内部类。内部类可以访问外部类的成员,包括私有成员,因为它们被视为外部类的友元。
注意:
1、此时的内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象区调用内部类。
2、外部类对内部类没有任何优越的访问权限。
3、内部类就是外部类的友元类,即内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元。
特性:
1、内部类可以定义在外部类的public、private以及protected这三个区域中的任一区域。
2、内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。
3、外部类的大小与内部类的大小无关。
class A//外部类
{
public:
class B//内部类
{
private:
int _b;
};
private:
int _a;
};
int main() {
cout << sizeof(A) << endl;//外部类的大小:4
return 0;
}
这里外部类A的大小为4,与内部类的大小无关。
C++ 匿名对象是指在没有被命名的情况下创建的临时对象。它们通常用于以下三种场景:
Cat();
会生成一个匿名对象,执行完后就会被析构。A a = 11;
相当于 A a = A(11);
,这里的 A(11)
就是一个匿名对象。return temp;
会先生成一个匿名对象,然后返回给调用者。匿名对象的生命周期取决于是否有其他对象接收它。如果有,那么匿名对象的生命周期就变成了接收对象的生命周期;如果没有,那么匿名对象就会在使用完后立即被销毁。
class A {
public:
A(int a = 0)
: _a(a) {
cout << "A(int a)" << endl;
}
~A() {
cout << "~A()" << endl;
}
private:
int _a;
};
class Solution {
public:
int Sum_Solution(int n) {
//...
return n;
}
};
int main() {
A aa1;
// 不能这么定义对象,因为编译器无法识别下面是一个函数声明,还是对象定义
//A aa1();
// 但是我们可以这么定义匿名对象,匿名对象的特点不用取名字,
// 但是他的生命周期只有这一行,我们可以看到下一行他就会自动调用析构函数
A();
A aa2(2);
// 匿名对象在这样场景下就很好用,当然还有一些其他使用场景,这个我们以后遇到了再说
Solution().Sum_Solution(10);
return 0;
}
在传参和传返回值的过程中,一般编译器会做一些优化,减少对象的拷贝,这个在一些场景下还 是非常有用的。
class A {
public:
A(int a = 0)
: _a(a) {
cout << "A(int a)" << endl;
}
A(const A &aa)
: _a(aa._a) {
cout << "A(const A& aa)" << endl;
}
A &operator=(const A &aa) {
cout << "A& operator=(const A& aa)" << endl;
if (this != &aa) {
_a = aa._a;
}
return *this;
}
~A() {
cout << "~A()" << endl;
}
private:
int _a;
};
void func1(A aa) {
}
void func2(const A &aa) {
}
int main() {
A aa1 = 1; // 构造+拷贝构造 -> 优化为直接构造
func1(aa1); // 无优化
func1(2); // 构造+拷贝构造 -> 优化为直接构造 创建一个临时对象,调用构造函数,赋值调用拷贝构造
func1(A(3));// 构造+拷贝构造 -> 优化为直接构造 A(3)构造 传参拷贝构造
// 加引用就没有优化了,因为引用是别名,没有拷贝
func2(aa1);
func2(2);// 如果func2的参数不是const类型,会出现报错。aa1会创建一个临时对象,这个对象具有常属性,所以func2的参数要设置为const
func2(A(3));
return 0;
}
A func3() {
A aa; //构造
return aa;//拷贝构造
}
A func4() {
return A();
}
int main() {
func3();//不会优化,因为函数体里面有多个表达式,编译器无法优化
A aa1 = func3();//构造 + 拷贝构造 + 拷贝构造 -> 优化为一个拷贝构造
func4(); //构造+拷贝构造 -- 优化为拷贝构造
A aa2 = func4();//构造 + 拷贝构造 + 拷贝构造 -> 优化为一个拷贝构造
return 0;
}
对象返回总结:
1、接收返回值对象,尽量拷贝构造方式接收,不要赋值接收(赋值是已经定义了对象,在赋值,编译器无法优化)
2、函数中返回对象时,尽量返回匿名对象
函数传参总结: 尽量使用const &传参