【C++】类与对象(四)——初始化列表|explicit关键字|static成员|友元|匿名对象

前言:

初始化列表,explicit关键字,static成员,友元,匿名对象


文章目录

一、构造函数的初始化列表

1.1 构造函数体内赋值

cpp 复制代码
class Date{
public:
	Date(int year, int month, int day){
		//赋值,并非初始化
		_year = year;
		_month = month;
		_day = day;
	}
private:
	int _year;
	int _month;
	int _day;
};

构造函数调用之后,在函数体中给成员变量赋值,但这并不能称为初始化 ,因为初始化是在变量或对象的创建时进行的,如果有初始化,那么仅有一次只有一次,而构造函数体内可以多次赋值。

对于某些类型的成员变量,必须进行初始化。函数体内并不能满足这些类型的成员变量进行初始化,因此有了初始化列表的概念。


1.2 初始化列表

初始化列表用于在构造函数中初始化类成员变量的语法结构:以一个冒号开始,接着是一个以逗号分隔的数据成员列表,每个"成员变量"后面跟一个放在括号中的初始值或表达式。

_year _month _day被year,month,day进行初始化

cpp 复制代码
Date(int year, int month, int day) :
		_year(year), 
		_month(month), 
		_day(day) {
		//函数体内其他操作
}	
  1. 实际上,即使没有显式写出初始化列表,成员变量依然会走初始化列表,但是因为没有初始值,因此成员变量是默认值。且初始化列表的顺序是成员变量声明的顺序。

  2. 以下成员,必须放在初始化列表位置进行初始化

    • 引用成员变量
    • const成员变量
    • 自定义类型成员(且该类没有默认构造函数时)
    cpp 复制代码
    class A{
    public:
    	A(int a):_a(a){
    
    	}
    private:
    	int _a;
    };
    
    class B{
    private:
    	A _aobj;      // 自定义类型且没有默认构造函数
    	int& _ref;    // 引用
    	const int _n; // const 
    public:
    	// _aobj(a)可以理解为调用A类的构造函数
    	B(int a, int ref):_aobj(a), _ref(ref), _n(0){
    		// 其他操作
    	}
    };
    
    int main() {               
    	B b(10,5);
    	return 0;
    }
  3. 成员变量声明时提供缺值

    当成员变量没有在初始化列表初始化时,成员变量被默认初始化为缺省值。

    cpp 复制代码
    class Date{
    public:
    	Date(int year, int month, int day) :
    		_month(month),
    		_day(day) {
    		//函数体内其他操作
    	}
    private:
    	int _year = 1;
    	int _month;
    	int _day;
    };
    
    int main() {
    	//B b();
    	Date d(2024, 1, 31);
    	return 0;
    }

通常初始化列表与构造函数体内赋值混合使用。


二、explicit关键字

  1. 单参数构造函数可以支持隐式类型的转换。

    意思是允许通过给定类型的单一参数将对象从一种类型转换为另一种类型,而无需显式调用构造函数。

    cpp 复制代码
    class Distance {
    private:
        double meters;
    
    public:
        // 单参数构造函数,允许从 double 类型隐式转换为 Distance 类型
        Distance(double m) : meters(m) {}  
    };
    
    int main() {
        // 隐式类型转换:double 到 Distance
        Distance d1 = 10.5;
    
        // 显式类型转换也是可行的
        Distance d2 = Distance(15.2);
        return 0;
    }

    在这个例子中,Distance 类具有一个单参数构造函数,允许将 double 类型的值隐式转换为 Distance 类型。当我们使用 Distance d1 = 10.5; 时,编译器会自动调用单参数构造函数,将 10.5 隐式转换为 Distance 类型的对象 d1

  2. C++11及之后的标准中,引入了一种新的特性,即"允许多参数的构造函数用于隐式类型转换"

    例如:

    cpp 复制代码
    class MyClass {
    public:
        // 多参数的构造函数
        MyClass(int x, double y) {
            // 构造函数逻辑
            std::cout << "Constructing MyClass with parameters: " << x << ", " << y << std::endl;
        }
    };
    
    int main() {
        // 隐式类型转换,调用构造函数
        MyClass obj = {42, 3.14};
    
        return 0;
    }

    在这个例子中,MyClass 类具有一个接受两个参数的构造函数。使用初始化列表 {42, 3.14} 进行对象的初始化时,实际上发生了隐式类型转换,将两个参数传递给构造函数。

  3. 当一个构造函数被 explicit 修饰时,它禁止隐式类型转换,只允许显式调用。

    cpp 复制代码
    explicit Distance(double m) : meters(m) {}
    cpp 复制代码
    // 隐式类型转换会引发编译错误
    Distance d1 = 10.5;  // 错误
    
    // 必须使用显式类型转换
    Distance d2 = Distance(15.2);  // 正确

三、static成员

声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量;用

static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化

  1. 静态数据成员: 静态数据成员是属于类而不是类的对象的成员变量。所有类的对象共享相同的静态数据成员。

    cpp 复制代码
    class MyClass {
    public:
        // 构造函数,用于增加 count
        MyClass() {
            count++;
        }
        int& GetCount() {
            return count;
        }
    private:
        // 静态数据成员
        static int count;
    };
    
    // 初始化静态数据成员
    int MyClass::count = 0;
    
    int main() {
        // 创建对象,增加 count
        MyClass obj1;
        MyClass obj2;
    
        // 访问静态数据成员
        std::cout << "Count: " << obj1.GetCount() << std::endl;
        std::cout << "Count: " << obj2.GetCount() << std::endl;
    
        return 0;
    }
  2. 静态成员函数: 静态成员函数是不依赖于类的实例而存在的函数。它没有访问类的非静态成员,因为它不通过实例来调用,而是通过类名和范围解析运算符 :: 来调用。

    cpp 复制代码
    class MathOperations {
    public:
        // 静态成员函数,用于加法运算
        static int add(int a, int b) {
            return a + b;
        }
    };
    
    int main() {
        // 调用静态成员函数
        int result = MathOperations::add(3, 5);
        std::cout << "Result: " << result << std::endl;
    
        return 0;
    }

特性总结

  1. 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区
  2. 静态成员变量必须在类外定义,定义时不添加static关键字,类中只是声明
  3. 类静态成员即可用 类名::静态成员 或者 对象.静态成员 来访问
  4. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
  5. 静态成员也是类的成员,受publicprotectedprivate 访问限定符的限制

静态成员函数可以调用非静态成员函数吗?

非静态成员函数可以调用类的静态成员函数吗?

静态成员函数可以调用非静态成员函数,但非静态成员函数不能直接调用类的静态成员函数。这是因为静态成员函数在调用时没有实例上下文,而非静态成员函数必须通过实例来调用。


四、友元

友元(Friend)可以用来解决类的封装性和访问控制方面的一些限制。友元可以是函数、类或者整个类中的函数。友元的存在允许特定的外部实体访问类的私有或受保护成员。

4.1 友元函数

友元函数:使用 friend 关键字声明一个非成员函数,允许该非成员函数访问该类的私有成员。

下面是一个简单的示例:

友元函数的声明通常放在类的声明中,友元函数的定义放在类的外部。

cpp 复制代码
class MyClass {
private:
    int privateData;

public:
    MyClass(int data) : privateData(data) {}

    // 声明友元函数
    friend void displayPrivateData(const MyClass&);
};

// 定义友元函数
void displayPrivateData(const MyClass& obj) {
    std::cout << "Private data: " << obj.privateData << std::endl;
}

int main() {
    MyClass obj(42);
    displayPrivateData(obj); // 友元函数可以访问私有成员
    return 0;
}

displayPrivateData 函数被声明为 MyClass 的友元函数,因此它可以访问 MyClass 中的私有成员 privateData。在 main 函数中,我们可以直接调用 displayPrivateData 函数来显示 obj 对象的私有成员数据。

需要注意的是,友元函数不是类的成员函数,因此它们不能使用成员访问运算符 .-> 来访问私有成员,而是需要通过参数传递对象来访问。


4.2 友元类

友元类:将一个类将另一个类声明为友元,从而允许友元类访问该类的私有成员和受保护成员。

举例:

cpp 复制代码
class MyClass {
private:
    int privateMember;

    // 将 FriendClass 声明为友元类
    friend class FriendClass;

public:
    MyClass(int data) : privateMember(data) {}
};

class FriendClass {
public:
    void accessPrivateMember(const MyClass& obj) {
        // 友元类可以访问 MyClass 的私有成员
        int data = obj.privateMember;
    }
};

int main() {
    MyClass a(10);
    FriendClass b;
    b.accessPrivateMember(a);
    return 0;
}

在这个例子中,FriendClass 被声明为 MyClass 的友元类。因此,FriendClass 的成员函数 accessPrivateMember 可以访问 MyClass 中的私有成员 privateMember

友元类的存在使得特定的类能够共享私有成员,这在某些情况下可能很有用,但同时也可能破坏了封装性。因此,在使用友元类时需要权衡利弊,并确保其使用符合设计原则和需要。


五、内部类

内部类定义在另一个类内部的类,可以直接访问外部类的私有成员,而不需要通过对象来实现。

下面是一个简单的示例:

cpp 复制代码
class Outer {
private:
    int outerPrivate;

public:
    class Inner {
    public:
        void display(const Outer& outer) {
            std::cout << "Inner class accessing outerPrivate: " << outer.outerPrivate << std::endl;
        }
    };

    Outer(int value) : outerPrivate(value) {}

    void callInner() {
        Inner inner;
        inner.display(*this);
    }
};

int main() {
    //通过callInner调用display
    Outer outerObj(42);
    outerObj.callInner();

    //创建inner对象调用display
    Outer::Inner innerObj;
    innerObj.display(outerObj);
    return 0;
}

六、匿名对象

匿名对象是指在创建对象时不指定对象名的对象。在对象的类型后面直接加上一对括号可以创建匿名对象,而不提供对象名。

匿名对象没有对象名,只能在创建的语句中使用,并且通常在该语句执行结束后就会被销毁。

cpp 复制代码
class MyClass {
public:
    void display() {
        std::cout << "Object is displayed." << std::endl;
    }
};

int main() {
    // 创建匿名对象,并调用其成员函数
    MyClass().display();
    
    return 0;
}

如果你喜欢这篇文章,点赞👍+评论+关注⭐️哦!

欢迎大家提出疑问,以及不同的见解。

相关推荐
过过过呀Glik5 分钟前
在 Ubuntu 上安装 Muduo 网络库的详细指南
linux·c++·ubuntu·boost·muduo
蜀黍@猿1 小时前
【C++ 基础】从C到C++有哪些变化
c++
Am心若依旧4091 小时前
[c++11(二)]Lambda表达式和Function包装器及bind函数
开发语言·c++
zh路西法1 小时前
【C++决策和状态管理】从状态模式,有限状态机,行为树到决策树(一):从电梯出发的状态模式State Pattern
c++·决策树·状态模式
轩辰~1 小时前
网络协议入门
linux·服务器·开发语言·网络·arm开发·c++·网络协议
lxyzcm2 小时前
C++23新特性解析:[[assume]]属性
java·c++·spring boot·c++23
蜀黍@猿2 小时前
C/C++基础错题归纳
c++
雨中rain2 小时前
Linux -- 从抢票逻辑理解线程互斥
linux·运维·c++
ALISHENGYA3 小时前
全国青少年信息学奥林匹克竞赛(信奥赛)备考实战之分支结构(实战项目二)
数据结构·c++·算法
arong_xu3 小时前
现代C++锁介绍
c++·多线程·mutex