C++类和对象入门(三)
目录
前言
一、初始化列表
1.1定义
1.2 格式和语法
1.3与在函数内初始化的区别
1.4使用初始化列表的必要性
1.5成员变量默认值的使用(C++11)
1.6初始化的先后顺序
1.7初始化列表的总结
二、类型转换
2.1内置类型转化成类类型
2.2类类型之间的相互转换
2.3explicit关键字
2.3.1explicit关键字在内置类型与类类型转换
2.3.2explicit关键字在类类型与类类型转换
三、static成员变量
3.1static变量的特点
3.2static成员函数
3.3static成员函数的访问
3.4static成员变量的初始化
3.5 访问控制与静态成员
3.7static成员函数和成员变量的总结
结语
前言
朋友们好,今天在这里继续贯穿C++类和对象的基础知识讲解,大致包括三点:
初始化列表,类型转换,static成员详解
那么接下来就让我们一起进入学习吧!
一、初始化列表
1.1定义
初始化列表是在类的构造函数中,对成员进行初始化的一种特殊机制,初始化列表可以提高效率,尤其是对于一些特殊类型的变量,他是唯一的初始化方式(这个我们后面会讲到)
1.2 格式和语法
语法:初始化列表是在对应构造函数后面添加一个冒号,接着各个需要初始化的成员以逗号隔开,每个成员的后面都紧跟括号中的初始值或者表达式,例如:
class Myclass { public: Myclass(int a, int b) ;_a(a) ,_b(b) private: int _a; int _b; }
上面就是直接给类中的两个成员使用初始化列表进行初始化。
那么问题来了,在构造函数体内部同样可以对成员进行初始化,那么两者的区别和用法又有什么区别呢?
1.3与在函数内初始化的区别
- 内置类型:对于内置类型(int , char)等类型的变量,使用初始化列表初始化和在函数体内初始化的效率和结果是一模一样的。
- 类类型:对于类类型的成员,如果没有使用初始化链表进行初始化,那么成员变量会先调用默认构造函数进行初始化,再在函数体内进行赋值,相当于两次初始化操作,例如如下代码:
class Member { public: Member(int value = 5) : _value(value) {} private: int _value; }; class A { public: A(int x) { _member = Member(x); // 先默认构造后再赋值 } private: Member _member; };
上面这个例子,_member会先调用Member的默认构造函数,再在函数体通过X进行赋值,使用初始化列表完全可以写成这样,如下:
class A { public: A(int x) : _member(x) { // 直接通过初始化列表初始化 } private: Member _member; };
这样_member只初始化了一次,大大减小了性能的开销。
1.4使用初始化列表的必要性
- 效率问题:再函数如果出现内部开销比较大,或者内部逻辑不清晰的情况下,使用初始化列表一是可以提高函数的执行效率避免二次初始化,二是可以使得函数的逻辑更加清晰。
- 必须的情况:对于某些成员变量,如const类型,引用类型,没有构造函数的类类型成员,必须通过初始化列表进行初始化,在函数体内对这些成员进行初始化是不被允许的。
class Time { public: Time(int hour) :_hour(hour) private: int _hour; } class Myclass { public: Myclass(int a,int& b) :_a(a)//_a是一个const类型变量,初始化的时候必须使用初始化列表 ,_b(b)//_b是一个引用类型变量,初始化的时候必须使用初始化列表 ,_t(12)//_t是一个类类型变量,初始化的时候必须使用初始化列表 private: const int _a; int& _b; Time _t; }
1.5成员变量默认值的使用(C++11)
在C++11中引入了变量默认值的概念,如果成员变量没有在初始化列表中被显式初始化,那么在这个成员变量在声明的时候,可以通过为它提供一成员变量的默认值来弥补。
class Myclass { public: Myclass(int a, int b) :_a(a) private: int _a; int _b = 1;//默认值 }
1.6初始化的先后顺序
尽管在初始化列表中,成员的位置可以随意先后出现,但是他的初始化顺序仍然是按照声明中的顺序进行初始化的。
class Myclass { public: Myclass(int a , int b):_a(a),_b(b)//这里初始化的顺序是依照下面的先b后a private: int _b; int _a; }
但是,为了保证代码逻辑的一致性,建议初始化列表的顺序与声明的顺序相同。
1.7初始化列表的总结
- 每个函数都有初始化列表,即使你没有显式地写出他。
- 每个成员变量都必须被初始化,即使他没有在初始化列表中显式地初始化。
- 对于引用类型,const类型,没有默认构造函数的类类型成员,必须在初始化列表中进行初始化。
- C++11 允许在成员变量声明时提供默认值,这些默认值会在初始化列表中未显式初始化时使用。
- 初始化顺序取决于成员变量在类中的声明顺序,而不是它们在初始化列表中的顺序。
二、类型转换
在C++里,类型转换是将一个类型的数据转化成另一个类型的过程。对于类而言,C++允许将内置类型和类类型转化成其他类型。
对于类型转换,可以是显式的,也可以是隐式的,这里面涉及3个知识点:构造函数、转换运算符和explicit关键字。
2.1内置类型转化成类类型
C++中,可以通过定义带有内置类型参数的构造函数来将内置类型转化成类类型。
class Myclass { public: Myclass(int a) :_a(a) private: int _a; } int main() { Myclass 10;//将10隐式地转化成了Myclass的类类型 }
这里的Myclass = 10就是隐式类型转化的常见形式。
2.2类类型之间的相互转换
如果遇到了一个场景需要类B接受类A的一个类对象,当我们将类A的值直接赋值给B的时候,就会发生下面的类与类之间的隐式类型转换。
class A { public: A(int a1) :_a1(a) int Get()const{ return _a;}//const修饰,前文讲过,为了防止权限放大 private: int _a; } class B { public: B (const&A a):_b(a.Get()) private: int _b; } int main() { A obj(10); B obj = A obj;//A类型对象隐式转换成B类型 }
这是类类型之间的隐式类型转换的常见形式。
2.3explicit关键字
explicit关键字的作用是防止在隐式类型转换的时候,发生不必要的逻辑错误,用explicit关键字来修饰构造函数,这样可以禁止该构造函数参与隐式类型转换。
2.3.1explicit关键字在内置类型与类类型转换
class Myclass { public: explicit Myclass(int a) :_a(a) private: int _a; } int main() { Myclass = 10;//不能这样写,explicit修饰的构造函数不能参与隐式类型转化 Myclaa (10);//正确,需要显式调用构造函数 }
2.3.2explicit关键字在类类型与类类型转换
class A { public: explicit A(int a1) :_a1(a) int Get()const{ return _a;}//const修饰,前文讲过,为了防止权限放大 private: int _a; } class B { public: explicit B (const&A a):_b(a.Get()) private: int _b; } int main() { A objA(10); B objB = A obj;//不能这样写,explicit阻止了隐式类型转换 B objB(objA); }
三、static成员变量
static成员变量,静态变量,他是类的所有对象共享的变量,而不是每个对象独立拥有的。
静态变量只能存储在静态存储区,也就是全局范围内,并且只能在外部初始化。
3.1static变量的特点
- 共享:他不是一个对象专属的,二十所有类的对象都可以共享的。
- 独立性:static变量会储存在静态存储区,不会随着对象爱那个的创建和销毁来重新分配内存。
- 类外初始化:静态城院变量的初始化只能在类外初始化,不能在类内声明的位置给它赋值。
#include<iostream>
using namespace std;
class A {
public:
A() {
++_scount; // 每创建一个对象,计数加1
}
A(const A& t) {
++_scount; // 每调用拷贝构造函数,计数加1
}
~A() {
--_scount; // 每销毁一个对象,计数减1
}
static int GetACount() {
return _scount; // 返回当前对象的数量
}
private:
// 声明静态成员变量
static int _scount;
};
// 类外初始化静态成员变量
int A::_scount = 0;
int main() {
cout << "初始对象数量: " << A::GetACount() << endl; // 初始对象数量为 0
A a1, a2; // 创建两个对象,计数加2
A a3(a1); // 拷贝构造,计数加1
cout << "当前对象数量: " << A::GetACount() << endl; // 输出 3
return 0;
}
如前文所述,静态成员变量不单独属于某个对象,每个类A的对象在创建的时候都会给_scount加一,每销毁一个对象就会减一。
3.2static成员函数
静态成员函数是类里面的一个特殊的成员函数,它并不依赖具体的对象实例,可以通过类明直接调用,静态成员函数没有this指针,因此它只能访问类的静态成员变量或者静态成员函数,不能访问非静态成员。
示例:
#include<iostream>
using namespace std;
class A {
public:
A() {
++_scount;
}
A(const A& t) {
++_scount;
}
~A() {
--_scount;
}
static int GetACount() {
return _scount; // 静态成员函数
}
private:
// 声明静态成员变量
static int _scount;
};
// 类外初始化静态成员变量
int A::_scount = 0;
int main() {
A a1, a2; // 创建两个对象,计数加2
A a3(a1); // 拷贝构造,计数加1
cout << "当前对象数量: " << A::GetACount() << endl; // 通过类名直接访问静态成员函数
return 0;
}
3.3static成员函数的访问
上文我们已经讲过一种访问方式:通过类名访问。但是其实还有一种访问方式:通过对象访问。
int main() { cout << A::GetACount() << endl; // 通过类名直接访问静态成员函数 cout << a1.GetACount() << endl; return 0; }
其实二者的本质是相同的,都是通过类来访问静态成员函数。
3.4static成员变量的初始化
静态成员变量不能再类内进行初始化,只能在类外进行初始化。
因为静态成员变量储存在静态区中,而不是在对象中,由于静态成员变量的共享性,它们只在整个程序中存在一份,因此必须在类外进行初始化,以确保所有对象访问的都是同一份数据。
3.5 访问控制与静态成员
静态成员与普通成员一样,也受访问控制修饰符(
public
、protected
、private
)的限制。即使静态成员属于类,而不是对象,但它们仍然需要遵守访问控制规则。class A { private: static int _private_count; public: static int GetPrivateCount() { return _private_count; // 只能通过成员函数访问 } }; int A::_private_count = 0; int main() { // cout << A::_private_count; // 错误,无法访问 private 静态成员 cout << A::GetPrivateCount() << endl; // 通过静态成员函数访问 return 0; }
3.7static成员函数和成员变量的总结
在C++中,static成员为类提供了管理全局数据和类级别操作的强大机制。静态成员变量被所有对象共享,存储在静态存储区中,而静态成员函数则可以在没有对象的情况下通过类名直接调用。静态成员与普通成员一样,受访问控制修饰符的限制,可以是public、private或protected。同时,静态成员变量不能在类内初始化,必须在类外进行初始化。通过静态成员,我们可以方便地实现对象计数、全局状态管理等功能,这让类在不依赖对象实例的情况下,依然能够提供有用的功能。
结语
至此,本文介绍的关于C++入门的部分知识正式结束,日后我会更新更多关于C++的基础入门知识,如果本文能帮助到阅读文章的你,就请点赞转发收藏吧,您的支持也是我继续学习和更新的动力,感谢支持!!