上一章节:
八、重学C++—动态多态(运行期)-CSDN博客https://blog.csdn.net/weixin_36323170/article/details/147004745?spm=1001.2014.3001.5502
本章节代码:
cpp/cppClassAndFunc.cpp · CuiQingCheng/cppstudy - 码云 - 开源中国https://gitee.com/cuiqingcheng/cppstudy/blob/master/cpp/cppClassAndFunc.cpp
一、引言
学到这里,会深深被C++的灵活多变所折服,学习C++的过程仿佛遨游在瑰丽多彩的海底世界。类与函数便是其中极为精妙的招式。今天,就让我们一起重新踏上探索 C++ 类与函数的奇妙之旅,揭开它们神秘的面纱。就像古人云:“温故而知新,可以为师矣。” 重学 C++ 的类与函数,也能让我们对编程有全新的感悟。
二、重新认识类:类中成员和特殊类
1、内联函数:代码中的“瞬移大师“
内联函数,可以是类中的,也可以是普通函数。在常见的函数调用中,程序需要跳转去执行函数代码,执行完再跳转回来,这中间会消耗一些时间和资源。而内联函数呢,编译器会直接将函数代码嵌入到调用它的地方,因此这里常与宏函数对比。
例如:
/**
* 1、内联函数
*/
#include <iostream>
// 宏函数
# define ADD(a,b) (a+b)
// 内联函数
inline int add(int& a, int &b)
{
return a+b;
}
class optData{
public:
// 类中内联函数
inline int add(int& a, int &b){
return a+b;
}
};
int main()
{
int n = 5;
int m = 13;
std::cout << "sum1: " << add(n,m) << std::endl;
std::cout << "sum2: " << ADD(n,m) << std::endl;
optData opt;
std::cout << "sum3: " << opt.add(n,m) << std::endl;
return 0;
}
宏函数不推荐使用,宏函数在设计初,就存在以下缺点,(1)、类型不安全,无法进行类型检测(2)、无法调试(3)、无法进行递归调用(4)、可读性差。
使用场景:
(1)、短小且频繁调用的函数:当函数体代码量很少(通常不超过 3 - 5 行 ),且在程序中会被频繁调用时,适合定义为内联函数。
(2)、 模板函数:简短的模板函数在编译时会生成具体的函数实例,更容易被内联化。内联后的模板函数能提供更好的性能。
#include <iostream>
// 模板内联函数,返回两个数中较大的值
template <typename T>
inline T max(T a, T b) {
return a > b? a : b;
}
int main()
{
int n = 5;
int m = 13;
int iMax = max(n, m);
std::cout << "iMax: " << iMax << std::endl;
return 0;
}
优点:
减少函数调用开销;提高程序执行效率;增强代码可读性;类型安全检查;相比于宏函数方便可调式。
2、静态成员:不依赖对象存在,是类中的”常驻大使“
静态成员变量是类的所有对象共享的一个变量,它就像班级里的公共财产,不管哪个同学都可以使用它。
例如:
/**
* 1、静态成员
*/
#include <iostream>
class Student {
public:
Student(std::string name) :
m_name(name)
{
++m_totalStudents;
}
~Student() {
--m_totalStudents;
}
// 静态成员函数
static int getTotalStudents() {
// ++m_totalStudents; 错误静态成员函数中不能出现普通成员变量,这里仅能操作静态成员变量;
return m_totalStudents;
}
static std::string getSchoolName(){
return m_schoolName;
}
// 普通成员函数
int getStudentsCount()
{
return getTotalStudents();
}
private:
std::string m_name;
// 静态成员变量
static std::string m_schoolName;
static int m_totalStudents;
};
std::string Student::m_schoolName = "上海中学";
int Student::m_totalStudents = 0;
int main()
{
// 静态成员
Student stu1("小明");
Student stu2("小红");
std::cout << "学生总数:" << Student::getTotalStudents() << " 学校名:"<< Student::getSchoolName()<<std::endl;
std::cout << "小明知道的学生总数:" << stu1.getStudentsCount() << std::endl;
return 0;
}
注意:
(1)静态成员函数只能操作静态成员变量;
(2)静态成员属于整个类共有的,不依赖于类所创建的对象;
(3)普通成员函数可以修改静态成员变量,因为要注意线程安全;
3、const常量
3.1 、成员变量是常量
一经初始化便不能修改;
3.2、成员函数形参为常量
传入形参在代码段内不能修改;
3.3、成员函数返回值为常量
3.4、成员函数,后接const修饰
不能修改成员变量值;
例如:
#include <iostream>
class Student {
public:
Student(std::string name) :
m_name(name)
{
++m_totalStudents;
}
Student(std::string name, std::string strlocal) : m_name(name),
m_strLocal(strlocal)
{
++m_totalStudents;
}
~Student() {
--m_totalStudents;
}
// 静态成员函数
static int getTotalStudents() {
// ++m_totalStudents; 错误静态成员函数中不能出现普通成员变量,这里仅能操作静态成员变量;
return m_totalStudents;
}
static std::string getSchoolName(){
return m_schoolName;
}
// 普通成员函数
int getStudentsCount()
{
return getTotalStudents();
}
std::string getSchoolLocation(){
return m_strLocal;
}
void setSex(const std::string strSex){
// 传入的形参不能修改;
m_strSex = strSex;
}
const std::string getSex(){
return m_strSex;
}
// 不能修改所有成员变量
void getStudetCount(int &count) const{
count = m_totalStudents;
}
private:
const std::string m_strLocal{"上海"};
std::string m_name;
std::string m_strSex;
// 静态成员变量
static std::string m_schoolName;
static int m_totalStudents;
};
std::string Student::m_schoolName = "上海中学";
int Student::m_totalStudents = 0;
int main()
{
//const
Student stu1("小王");
stu1.setSex("男");
Student stu2("小丽");
stu2.setSex("女");
Student stu3("小张", "北京");
stu3.setSex("男");
int stuCount = 0;
stu2.getStudetCount(stuCount);
std::cout << "小王学校在哪儿:" << stu1.getSchoolLocation() << " 小王性别:" << stu1.getSex() << std::endl;
std::cout << "小丽学校在哪儿:" << stu2.getSchoolLocation() << " 小丽学校学生数量:" << stuCount <<std::endl;
std::cout << "小张学校在哪儿:" << stu3.getSchoolLocation() << std::endl;
return 0;
}
4、无法实例化对象的类
4.1、抽象类无法实例化对象(略)
4.2、构造函数为私有的
// 构造函数私有
class baseClass {
private:
baseClass(){
std::cout << "构造函数" <<std::endl;
}
private:
int m_num;
};
5、无法被继承的类final
class NonInheritable2 final {
public:
void print() {
std::cout << "This is a final class." << std::endl;
}
};
6、delete特殊用法(修饰构造函数)
在 C++ 里,在构造函数后面使用 = delete 是 C++11 引入的特性,其作用是显式地删除该构造函数。删除构造函数后,就无法再使用该构造函数来创建类的对象,这在一些场景下很有用,下面详细介绍:
6.1、禁止默认构造函数
当你不希望类拥有默认构造函数(即无参数的构造函数)时,可使用 = delete 将其删除。
#include <iostream>
class MyClass {
public:
MyClass(int value) : data(value) {}
// 删除默认构造函数
MyClass() = delete;
private:
int data;
};
int main() {
// MyClass obj; // 错误,默认构造函数已被删除
MyClass obj(42); // 正确,使用带参数的构造函数
return 0;
}
在上述代码中,MyClass的默认构造函数被删除,所以不能通过无参数方式构造对象;
6.2、禁止拷贝构造函数和拷贝赋值运算符
若你不希望类的对象被复制,可使用 = delete 删除拷贝构造函数和拷贝赋值运算符。
#include <iostream>
class NonCopyable {
public:
NonCopyable() = default;
// 删除拷贝构造函数
NonCopyable(const NonCopyable&) = delete;
// 删除拷贝赋值运算符
NonCopyable& operator=(const NonCopyable&) = delete;
};
int main() {
NonCopyable obj1;
// NonCopyable obj2 = obj1; // 错误,拷贝构造函数已被删除
// NonCopyable obj3;
// obj3 = obj1; // 错误,拷贝赋值运算符已被删除
return 0;
}
此代码中,NonCopyable 类的拷贝构造函数和拷贝赋值运算符都被删除,这就阻止了对象的复制操作。
三、友元
友元就像是类的 “好朋友”,虽然不在类的内部,但却能访问类的私有成员。它打破了类的封装性,但有时候为了提高代码的灵活性和效率,这种 “破格” 也是必要的。
比如:
#include <iostream>
class A {
public:
A(int data) : m_privateData(data) {}
protected:
double m_protectedData{3.88};
private:
friend void friendFunction(A& a); // 友元函数
friend class ClassB; // 友元类
private:
int m_privateData;
};
void friendFunction(A& a) {
std::cout << "访问到私有数据:" << a.m_privateData << std::endl;
}
class ClassB {
public:
void accessClassAData(A& obj) {
// 友元类可以访问 ClassA 的私有和受保护成员
std::cout << "Accessing private data of Class A: " << obj.m_privateData << std::endl;
std::cout << "Accessing protected data of Class A: " << obj.m_protectedData << std::endl;
}
};
int main()
{
// 友元
A a(10);
friendFunction(a);
ClassB b;
b.accessClassAData(a);
}
在上述代码中,ClassB 被声明为 A 的友元类,所以 ClassB 的成员函数 accessClassAData 能够访问 A 的私有成员 privateData 和受保护成员 protectedData。
使用场景
(1)、数据共享:当两个类之间存在紧密的关联,需要共享数据时,可以使用友元类。
注意事项
(1)、打破封装性:友元类破坏了类的封装性,因为它允许外部类访问本类的私有和受保护成员。过度使用友元类会导致代码的安全性和可维护性降低,所以应该谨慎使用。
(2)、单向性:友元关系是单向的。
(3)、不具有传递性。
四、名字空间
名字空间就像是编程世界里的 “房间分隔器”。当我们的项目越来越大,代码中的名字(变量名、函数名、类名等)可能会产生冲突,这时候名字空间就能把不同功能的代码隔离开来。
namespace Math {
int add(int a, int b) {
return a + b;
}
}
namespace Utility {
int add(int a, int b) {
return a * b;
}
}
int main() {
std::cout << "Math 名字空间的加法:" << Math::add(3, 5) << std::endl;
std::cout << "Utility 名字空间的加法:" << Utility::add(3, 5) << std::endl;
return 0;
}
这里定义了 Math 和 Utility 两个名字空间,它们都有 add 函数,但功能不同,通过名字空间的限定,我们可以准确调用到想要的函数。
五、总结
C++ 的类与函数蕴含着无尽的奥秘,从内联函数的高效执行,到静态成员的独特共享机制,再到各种特殊类和关键字的巧妙运用,以及友元和名字空间的神奇功能,每一处都值得我们细细品味。