从零开始学C++(4) --类和对象

一、初始化列表

初始化列表的定义

我们之前在学构造函数中提到,如果自定义类型没有默认构造函数,在进行初始化时就要用到初始化列表,初始化列表的使用方法是以冒号开始,以逗号分隔,每个成员变量后面跟一个括号,括号中是初始值或表达式。

初始化列表的特点:

  • 每个成员变量只能在初始化列表中出现一次,语法意义上可以认为初始化列表是每个成员变量定义初始化的地方。
  • 没有默认构造函数的自定义成员变量,引用成员变量,const 成员变量必须在初始化列表中初始化,否则会编译报错。
  • C++11 规定可以在声明时给定缺省值,给没有显示在初始化列表初始化的成员使用。
  • 尽量使用初始化列表进行初始化,因为不在初始化列表的成员也会走初始化列表,如果这个成员在声明位置给定了缺省值,就用缺省值进行初始化,如果没有给定,对于内置类型,编译器可能会初始化也可能不会,C++ 没有明确规定,对于自定义成员变量,则会去调用它的默认构造函数,如果没有默认构造函数,则会编译报错。
  • 初始化列表中按照成员变量在类中声明顺序进行初始化,跟成员在初始化列表出现的先后顺序无关。建议声明顺序和初始化列表顺序保持一致。
cpp 复制代码
#include <iostream>
using namespace std;

class Time
{
public:
	Time(int hour)
		:_hour(hour)
	{
		cout << "Time()" << endl;
	}
private:
	int _hour;
};

class Date
{
public:
	Date(int x, int year, int month, int day)
		:_year(year)
		, _month(month)
		, _day(day)
		,_t(12)
		,_ref(x)
		,_n(1)
	{

	}
	void Print() const
	{
		cout << _year << "-" << _month << "-" << _day << endl;
	}

private:
	//注意这里不是初始化,是给缺省值,在初始化列表没有显示初始化时就用这个缺省值初始化
	int _year = 1;
	int _month = 1;
	int _day;

	//这三种情况必须在初始化列表初始化
	Time _t = 12;  //没有默认构造函数
	int& _ref;     //引用
	const int _n = 1;  //const

};

二、类型转换

C++ 支持内置类型隐式转换为类类型对象,需要有以内置类型为参数的构造函数支持,构造函数前面加 explicit 就不再支持隐式类型转换。类类型的对象之间也可以隐式转换,需要相应的构造函数支持。

cpp 复制代码
class A
{
public:
	//explicit A(int a)
	A(int a)
		:_a1(a)
	{

	}
	//explicit A(int a1, int a2)
	A(int a1, int a2)
		:_a1(a1)
		, _a2(a2)
	{

	}
	void Print()
	{
		cout << _a1 << " " << _a2 << endl;
	}
	
	int Get() const
	{
		return _a1 + _a2;
	}
private:
	int _a1;
	int _a2;
};
class B
{
public:
	B(const A& a)
		:_b(a.Get())
	{}
private:
	int _b = 0;
};


int main()
{
	//Date d1;
	//d1.Print();
	A aa1 = 1;
	const A& aa2 = 1;

	A aa3 = { 2,2 };

	B b = aa3;
	const B& rb = aa3;
	return 0;
}

三、static 成员

  • 用 static 修饰的成员变量称为静态成员变量,在类内声明,类外初始化,静态成员变量为所有类对象所共享,不属于某个单独的对象,不存在对象中,存在静态区,静态成员变量不能在声明处给缺省值,因为缺省值是给构造函数初始化列表的,静态成员函数不属于某个对象,不走构造函数初始化列表。
  • 用 static 修饰的成员函数称为静态成员函数,静态成员函数没有 this 指针,静态成员函数可以访问其他静态成员,但不可以访问非静态,因为没有 this 指针。非静态成员函数可以任意访问静态成员变量和静态成员函数。
  • 突破类域就可以访问静态成员,可以通过 :: 成员变量或对象 . 成员变量来访问静态成员变量和静态成员函数。
  • 静态成员也是类的成员,受 public、protected、private 访问限定符的限制。
cpp 复制代码
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()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a1);
	cout << A::GetACount() << endl;
	cout << a1.GetACount() << endl;
	// 编译报错:error C2248: "A::_scount": 无法访问 private 成员(在"A"类中声明)
	//cout << A::_scount << endl;
	return 0;
}

四、友元

友元的概念

友元(friend)是 C++ 提供的一种突破类封装性的机制。它允许一个函数或另一个类访问当前类的私有(private)和保护(protected)成员。友元关系是单向的、非传递的,且不能被继承。

友元的三种形式

  • 友元函数:一个普通函数被声明为类的友元,可以访问该类的私有和保护成员。
  • 友元类:一个类被声明为另一个类的友元,则该类的所有成员函数都可以访问另一个类的私有和保护成员。
  • 友元成员函数:一个类的某个成员函数被声明为另一个类的友元,只有这个特定的成员函数可以访问另一个类的私有和保护成员。

友元函数示例

cpp 复制代码
#include <iostream>
using namespace std;

class Box {
private:
    int width;
    int height;
public:
    Box(int w, int h) : width(w), height(h) {}
    
    // 声明友元函数
    friend void printBox(const Box& box);
};

// 友元函数定义
void printBox(const Box& box) {
    // 可以直接访问私有成员
    cout << "Width: " << box.width << endl;
    cout << "Height: " << box.height << endl;
}

int main() {
    Box b(10, 20);
    printBox(b);  // 输出: Width: 10, Height: 20
    return 0;
}

友元类示例

cpp 复制代码
#include <iostream>
using namespace std;

class A {
private:
    int secret = 42;
    
    // 声明 B 为友元类
    friend class B;
};

class B {
public:
    void showSecret(const A& a) {
        // B 可以访问 A 的私有成员
        cout << "A's secret: " << a.secret << endl;
    }
};

int main() {
    A a;
    B b;
    b.showSecret(a);  // 输出: A's secret: 42
    return 0;
}

友元成员函数示例

cpp 复制代码
#include <iostream>
using namespace std;

class B;  // 前向声明

class A {
private:
    int value = 100;
    
    // 声明 B 类的 display 函数为友元
    friend void B::display(const A& a);
};

class B {
public:
    void display(const A& a) {
        // 只有这个函数可以访问 A 的私有成员
        cout << "A's value: " << a.value << endl;
    }
    
    void cannotAccess(const A& a) {
        // 这个函数不能访问 A 的私有成员
        // cout << a.value << endl;  // 编译错误
    }
};

int main() {
    A a;
    B b;
    b.display(a);  // 输出: A's value: 100
    return 0;
}

友元的特点和注意事项

  • 单向性:A 是 B 的友元,不代表 B 是 A 的友元。
  • 非传递性:A 是 B 的友元,B 是 C 的友元,不代表 A 是 C 的友元。
  • 不能被继承:友元关系不能被派生类继承。
  • 破坏封装:友元破坏了类的封装性,应谨慎使用。
  • 声明位置:友元声明可以在类的任何位置(public、private、protected),效果相同。
  • 运算符重载:友元常用于运算符重载,特别是需要访问私有成员的运算符。

友元在运算符重载中的应用

cpp 复制代码
#include <iostream>
using namespace std;

class Complex {
private:
    double real;
    double imag;
public:
    Complex(double r = 0, double i = 0) : real(r), imag(i) {}
    
    // 声明友元运算符函数
    friend Complex operator+(const Complex& c1, const Complex& c2);
    friend ostream& operator<<(ostream& os, const Complex& c);
};

// 友元运算符函数定义
Complex operator+(const Complex& c1, const Complex& c2) {
    return Complex(c1.real + c2.real, c1.imag + c2.imag);
}

ostream& operator<<(ostream& os, const Complex& c) {
    os << c.real << " + " << c.imag << "i";
    return os;
}

int main() {
    Complex c1(3.0, 4.0);
    Complex c2(1.0, 2.0);
    Complex c3 = c1 + c2;
    cout << c3 << endl;  // 输出: 4 + 6i
    return 0;
}

总结

友元是 C++ 中一种特殊的访问控制机制,它提供了对类私有成员的有限访问权限。虽然友元破坏了封装性,但在某些场景下(如运算符重载、紧密协作的类之间)可以提高代码的灵活性和效率。使用时需要权衡封装性和便利性,避免过度使用导致代码耦合度过高。

五、内部类

内部类的概念

内部类(Inner Class)是定义在另一个类内部的类。它是一个独立的类,有自己的作用域和访问权限。

内部类的特点

  • 内部类可以直接访问外部类的静态成员,无需外部类对象。
  • 外部类不能直接访问内部类的私有成员,需要创建内部类对象。
  • 内部类可以定义在 public、protected、private 区域,影响外部访问权限。
  • 用 static 修饰的内部类称为静态内部类,不能访问外部类的非静态成员。

简单示例

cpp 复制代码
#include <iostream>
using namespace std;

class Outer {
private:
    static int count;
public:
    class Inner {
    public:
        void show() {
            cout << "Outer::count = " << Outer::count << endl;
        }
    };
};

int Outer::count = 10;

int main() {
    Outer::Inner obj;
    obj.show();  // 输出: Outer::count = 10
    return 0;
}

主要用途

  • 封装只在类内部使用的辅助类。
  • 实现迭代器模式。
  • 避免全局命名冲突。

六、匿名对象

匿名对象的概念

匿名对象是没有名字的临时对象,创建后立即使用,表达式结束即销毁。

匿名对象的特点

  • 生命周期仅限于创建它的表达式。
  • 无需变量名,创建后立即使用。
  • 是右值,不能取地址(const 引用除外)。

基本用法

cpp 复制代码
#include <iostream>
using namespace std;

class Test {
public:
    Test(int v) { cout << "构造:" << v << endl; }
    ~Test() { cout << "析构" << endl; }
    int getValue() { return 100; }
};

int main() {
    // 创建匿名对象并调用成员函数
    Test(10).getValue();
    
    // 作为函数参数
    int sum = Test(1).getValue() + Test(2).getValue();
    
    // const 引用延长生命周期
    const Test& ref = Test(30);
    return 0;
}

常见场景

  • 函数返回临时对象。
  • 链式调用。
  • 临时计算。

注意事项

  • 生命周期短,不能保存指针(const 引用除外)。
  • 频繁创建销毁可能影响性能。
  • 调试时难以跟踪。
相关推荐
haluhalu.1 小时前
prompts.chat:07-few-shot-prompting
人工智能·算法
Wang's Blog1 小时前
Go-Zero框架上手前奏2: 微服务核心要素 —— 拆分、通信与无状态设计
开发语言·微服务·golang
Jerry1 小时前
LeetCode 150. 逆波兰表达式求值
算法
浩瀚地学1 小时前
【面试算法笔记】0106-数组-区间和
笔记·算法·面试
Jerry7 小时前
LeetCode 1047. 删除字符串中的所有相邻重复项
算法
可编程芯片开发9 小时前
基于霍尔传感器和PID控制器的有功功率检测控制系统simulink建模与仿真
算法
To_OC10 小时前
LC 17 电话号码的字母组合:我的回溯算法,就是从这道题开窍的
javascript·算法·leetcode
blueman888810 小时前
Qt5通过vcpkg中调用时,在debug模式下调试时总是调用release的plugins文件夹中的dll
c++·qt·cmake