const只读
在 C++ 中,const
关键字用于声明一个变量为常量,意味着一旦被初始化之后,它的值就不能被改变。
声明常量:
使用 const
关键字可以声明变量为常量。这意味着这个变量的值不能被修改。
const int MAX_SIZE = 100;
指针与 const
:
const
可以与指针结合使用,用来声明指向常量的指针或者指针本身为常量。
指向常量的指针:不能通过这个指针来修改它所指向的值。
const int* ptr = &MAX_SIZE;
常量指针:指针本身的值(即存储的地址)不能被修改。
int value = 5;
int* const ptr = &value;
指向常量的常量指针:既不能修改指针存储的地址,也不能通过指针修改所指向的值。
const int* const ptr = &MAX_SIZE;
指针与const结合使用,主要看const后面紧跟着的是什么,如果紧跟着的是int* 数据类型,说明指针上的值不能改变,如果紧跟着的是指针名,说明指针本身不能改变,如果都存在,都无法改变。
const
成员函数:
在类中,如果一个成员函数不修改任何成员变量,那么这个函数可以被声明为 const
。
class MyClass {
public:
int getValue() const {
return value;
}
private:
int value;
};
const
与对象:
当一个对象被声明为 const
,那么只能调用它的 const
成员函数。这是因为 const
成员函数保证不会修改对象的状态。
我们可以思考以下几个问题,
1. const对象可以调用非const成员函数吗?---const对象只能调用const成员函数
2. 非const对象可以调用const成员函数吗?---非const意味着可读可写,对于只读成员函数当然可以调用
3. const成员函数内可以调用其它的非const成员函数吗?---const成员函数不能修改成员变量,自然不能调用非const成员函数。
4. 非const成员函数内可以调用其它的const成员函数吗?---非const意味着可读可写,对于只读成员函数当然可以调用
初始化列表
在 C++ 中,初始化列表(Initializer List)是一个非常重要的特性,用于在构造函数中初始化类的成员变量。使用初始化列表可以提高代码的效率和可读性,并且在某些情况下是必要的。
基本语法
初始化列表紧随在构造函数的参数列表后面,由一个冒号 (:) 引入,后跟一个或多个用逗号分隔的初始化器。每个初始化器包括成员变量名称和用于初始化该成员的值或表达式。
/*初始化列表*/
#include <iostream>
using namespace std;
class Date {
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
void Show() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2024, 1, 31);
d1.Show();
}
初始化列表相对于构造函数的优点
更高的效率
初始化列表直接初始化成员变量,而不是先默认构造它们然后再赋值。这在成员变量是对象时尤其重要,因为它避免了额外的构造和析构调用。
必要性
对于 const
成员和引用类型的成员变量,它们必须在初始化列表中初始化,因为这些类型的变量一旦被默认构造,就不能再被赋予新值。
初始化列表对于非基本数据类型的优势
对于类类型的成员变量,如果它们没有默认构造函数,或者有参数的构造函数更高效,使用初始化列表可以明确指定如何初始化这些成员。
对于const成员和引用类型的成员变量
/*构造函数的缺陷---难以初始化 const 和引用类型成员变量*/
#include <iostream>
using namespace std;
class A{
public:
A(int& a){
_ref= a;
_n=10;
}
private:
int& _ref;
const int _n;
};
构造函数并不是严格意义上的初始化,上面代码尝试用构造函数初始化const和引用成员会报错,“[错误]在‘ int &’[-fpermissive ]中未初始化的引用成员”,原因是构造函数实际上是对已经初始化的成员变量进行赋值操作,也就是说成员变量已经被创建,但构造函数是对创建好的成员变量进行赋值操作。我们希望的初始化是在创建的过程中进行赋值操作。
对于const和引用成员,必须在创建的时候对其初始化,而构造函数是对创建好的对象进行赋值,因此只能使用初始化列表进行严格的初始化。
/*构造函数的缺陷---难以初始化 const 和引用类型成员变量*/
#include <iostream>
using namespace std;
class A {
public:
A(int& a):
_ref(a),
_n(10)
{}
void Show() {
cout << _ref << "---" << _n << endl;
cout << &_ref << endl;
}
private:
int& _ref;
const int _n;
};
int main() {
int b = 100;
A a(b);
cout << &b << endl;
a.Show();
return 0;
}
对于自定义类型成员变量
/*初始化列表对于非基本数据类型的优势*/
#include <iostream>
using namespace std;
class A {
public:
A(int a): _a(a)
{}
void Show() {
cout << "_a:" << _a << endl;
}
private:
int _a;
};
class B {
public:
B(int a, int ref)
: _aobj(a),
_ref(ref),
_n(10)
{}
void Show() {
_aobj.Show();
cout << "_ref:" << _ref << endl << "_n:" << _n << endl;
}
private:
A _aobj;
int& _ref;
const int _n;
};
int main() {
int c = 20;
B b(100, c);
b.Show();
}
初始化列表的初始化顺序
成员变量的初始化顺序与它们在类中的声明顺序一致,而不是初始化列表中的顺序。这可以确保即使成员变量之间有依赖关系,它们也总是以一致的顺序被初始化。
/*初始化列表---初始化顺序*/
#include <iostream>
using namespace std;
class Date {
public:
Date(int year, int month, int day)
: _year(year)
, _month(month)
, _day(day)
{}
void Show() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _day;
int _month;
int _year;
};
int main() {
Date d1(2024, 1, 31);
d1.Show();
}
explicit 关键字
在 C++ 中,explicit
关键字用于阻止类构造函数的隐式自动类型转换。它主要用在类的单参数构造函数上,或者在构造函数有默认参数时,构造函数虽然有多个参数,但可以仅通过一个实际参数调用。
在没有 explicit
关键字的情况下,C++ 允许单参数构造函数隐式地将一个值转换为其类类型。这种隐式转换有时可能导致意料之外的行为。
/*explicit关键字*/
#include <iostream>
using namespace std;
class Date {
public:
Date(int year)
: _year(year),
_month(0),
_day(0)
{}
Date& operator=(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void Show() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2022);
d1.Show();
d1 = 2023;//Date d(2023)
d1.Show();
return 0;
}
当我们将整型的2023传入d1时,发生隐式转换将整型2023转化为日期类,调用单参数构造函数,以整型2023为参数构造日期类作返回值。
此时在Date构造函数前面添加explicit关键字,阻止类构造函数的隐式自动类型转换。
/*explicit关键字*/
#include <iostream>
using namespace std;
class Date {
public:
explicit Date(int year)
: _year(year),
_month(0),
_day(0)
{}
Date& operator=(const Date& d) {
if (this != &d) {
_year = d._year;
_month = d._month;
_day = d._day;
}
return *this;
}
void Show() {
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2022);
d1.Show();
d1 = 2023;//Date d(2023)
d1.Show();
return 0;
}
static成员
在 C++ 中,static
关键字用于定义类的静态成员,包括静态成员变量和静态成员函数。这些成员与类的特定实例无关,而是属于类本身。
静态成员变量
类的静态成员变量在所有实例之间共享。也就是说,不管创建了多少对象,静态成员变量只有一份副本。
静态成员变量可以通过类名直接访问,而不需要类的实例。
静态成员变量必须在类定义外进行初始化(通常在源文件中)。
静态成员函数
静态成员函数可以访问类的静态成员变量和其他静态成员函数,但不能直接访问类的非静态成员。(静态函数没有this指针)
静态成员函数可以通过类名直接访问,而不需要类的实例。
/*静态static*/
#include<iostream>
using namespace std;
class A{
public:
static void add(){
n++;
}
static int n;
};
int A::n=0;
int main(){
cout<<A::n<<endl;
A a;
a.n++;
A b;
cout<<b.n<<endl;
A::add();
cout<<A::n<<endl;
}
。
尝试在类内重载<<运算符
/*在类内 重载<<与>>运算符*/
#include <iostream>
using namespace std;
class Date {
public:
Date(int year, int month, int day)
: _year(year),
_month(month),
_day(day)
{}
ostream& operator<<(ostream& _cout) {
_cout << _year << "-" << _month << "-" << _day << endl;
return _cout;
}
private:
int _year;
int _month;
int _day;
};
int main() {
Date d1(2024, 1, 31);
//_cout << d1 << endl;
d1 << cout << endl;
}
我们在类内重载<<运算符,以至于我们使用<<时,左边必须是Date类的对象,右边才是第一个参数_cout。因为<<左边的Date对象需要作为this指针传入operator<<函数中,正常来说我们希望<<左边作为_cout参数右边作为this指针传入函数内,因此在类内不便于我们重载<<与>>运算符。
尝试在类外重载<<运算符
/*在类内 重载<<与>>运算符*/
#include <iostream>
using namespace std;
class Date {
public:
Date(int year, int month, int day)
: _year(year),
_month(month),
_day(day)
{}
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d) {
_cout << d._year << "-" << d._month << "-" << d._day << endl;
return _cout;
}
int main() {
Date d1(2024, 1, 31);
cout << d1 << endl;
}
在类外外面重载<<与>>运算符有一个严重的问题,无法在类外访问类内被private修饰的成员变量。
友元
在C++中,友元(Friend)是一种机制,它允许一个类或函数访问另一个类的私有成员。这意味着友元能够绕过C++中的封装性,使得外部类或函数可以直接访问另一个类的私有数据和成员函数。
类友元:
友元可以是一个类,这意味着一个类可以允许另一个类访问它的私有成员。
在一个类中声明另一个类为友元,可以在类的定义中使用 friend
关键字。
class ClassA {
private:
int privateVarA;
public:
ClassA() : privateVarA(0) {}
friend class ClassB; // 声明ClassB为ClassA的友元
};
class ClassB {
public:
void AccessPrivateVar(ClassA& obj) {
obj.privateVarA = 42; // ClassB可以访问ClassA的私有成员
}
};
函数友元:
友元也可以是一个函数,这意味着一个函数可以访问一个类的私有成员。
在一个类中声明一个函数为友元,可以在类的定义中使用 friend
关键字。
class MyClass {
private:
int privateVar;
public:
MyClass() : privateVar(0) {}
friend void FriendFunction(MyClass& obj); // 声明FriendFunction为MyClass的友元函数
};
void FriendFunction(MyClass& obj) {
obj.privateVar = 42; // FriendFunction可以访问MyClass的私有成员
}
在类外重载<<运算符
/*在类内 重载<<与>>运算符*/
#include <iostream>
using namespace std;
class Date {
public:
Date(int year, int month, int day)
: _year(year),
_month(month),
_day(day)
{}
friend ostream& operator<<(ostream& _cout, const Date& d);
private:
int _year;
int _month;
int _day;
};
ostream& operator<<(ostream& _cout, const Date& d) {
_cout << d._year << "-" << d._month << "-" << d._day << endl;
return _cout;
}
int main() {
Date d1(2024, 1, 31);
cout<<d1<<endl;
}
在类内声明ostream& operator<<(ostream& _cout, const Date& d);这个函数是Date的友元函数。表示这个函数可以访问Date类内的私有成员。
内部类
在C++中,内部类(也称为嵌套类)是定义在另一个类中的类。这种结构允许组织代码的方式更加紧密和结构化,使得外部类和内部类之间的关系更加明确。内部类可以访问外部类的私有成员,这是因为C++标准规定内部类是外部类的友元。
定义内部类
内部类(嵌套类)是在另一个类的内部定义的类。这使得嵌套类能够访问外部类的所有成员(包括私有成员)。
class OuterClass {
public:
// 外部类的成员
class InnerClass {
public:
// 内部类的成员
};
};
访问外部类成员
嵌套类可以直接访问外部类的成员,包括私有成员。因为C++标准规定内部类是外部类的友元。
class OuterClass {
int value = 0; // 私有成员
public:
class InnerClass {
public:
void display(OuterClass& outer) {
cout << outer.value << endl; // 直接访问外部类的私有成员
}
};
};
实例化内部类
要实例化嵌套类,您首先需要外部类的实例,然后使用该实例来实例化内部类。
如果内部类是公有的,也可以直接从外部类的外部实例化它。否则,你可能需要在外部类内部或通过外部类的公有方法来实例化内部类。
/*内部类*/
#include <iostream>
using namespace std;
class OuterClass {
int value = 0; // 私有成员
public:
class InnerClass {
public:
void display(OuterClass& outer) {
cout << outer.value << endl; // 直接访问外部类的私有成员
}
};
};
int main() {
OuterClass a;
OuterClass::InnerClass b;
b.display(a);
}
匿名对象
在C++中,匿名对象是指没有名称的对象。它们通常在创建对象时立即使用,而不需要将其存储在变量中。匿名对象可以用于各种场景,比如作为函数参数、作为表达式的一部分或在任何只需要临时对象的地方。
匿名对象在使用后立即被销毁,这意味着它们的生命周期非常短。这使得它们在处理不需要长期存储的数据时非常有用。
作为函数参数
假设我们有一个函数display
,它接受一个对象参数,并显示一些信息。我们可以在调用display
时创建一个匿名对象:
#include <iostream>
class MyClass {
public:
void show() const {
std::cout << "MyClass show function called" << std::endl;
}
};
void display(const MyClass& obj) {
obj.show();
}
int main() {
// 使用匿名对象作为函数参数
display(MyClass());
return 0;
}
类方法链式调用
在某些情况下,你可能想要立即调用一个对象的方法,而不是先将其存储在变量中。可以创建一个匿名对象来实现这一点:
#include<iostream>
class MyClass {
public:
void show() const {
std::cout << "MyClass show function called" << std::endl;
}
};
int main() {
// 创建一个匿名对象并立即调用它的方法
MyClass().show();
return 0;
}
短暂的生命周期
/*短暂的生命周期*/
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main(){
A();
return 0;
}
匿名对象的生命周期只有一行。
拷贝构造优化
1.构造函数构造出来的对象A,用来拷贝构造一个对象B,优化成,构造函数直接构造出来的对象B(直接用对象A)
2.函数值返回一个对象A,优化成直接返回A,A用来拷贝构造一个对象B,优化成,直接用对象A,A生命周期延长。
/*拷贝构造的优化*/
#include <iostream>
using namespace std;
class A {
public:
A(int a = 0)
: _a(a) {
cout << "A(int a=0)" << 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 f1(A aa) {
}
A f2() {
A aa;
return aa;
}
int main() {
A aa1;
f1(aa1);
cout << endl;
f2();
cout << endl;
f1(1);//构造函数构造出来的对象A,用来拷贝构造一个对象B,优化成,构造函数直接构造出来的对象B(直接用对象A)
f1(A(2));//构造函数构造出来对象A,用来拷贝构造一个对象B,优化成,构造函数直接构造出来的对象B(直接用对象A)
cout << endl;
A aa2 = f2();//函数返回一个对象A,用来拷贝构造一个对象B,优化成,直接用对象A,A生命周期延长
cout << endl;
aa1 = f2();//函数返回一个对象A,赋值重载,无法优化
cout << endl;
return 0;
}
结尾
最后,感谢您阅读我的文章,希望这些内容能够对您有所启发和帮助。如果您有任何问题或想要分享您的观点,请随时在评论区留言。
同时,不要忘记订阅我的博客以获取更多有趣的内容。在未来的文章中,我将继续探讨这个话题的不同方面,为您呈现更多深度和见解。
谢谢您的支持,期待与您在下一篇文章中再次相遇!