C++初阶——类与对象(下篇)

一、再谈构造函数

1.1初始化列表

之前的博客谈到,构造函数的本意是给对象的各个成员变量一个合适的初始值。这里存在着一个问题,即成员变量的创建和初始化是分离的 。但是,在某些成员变量在定义时必须被初始化。怎么解决呢?

C++引入了初始化列表来解决这个问题。

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. 引用成员变量,const成员变量,自定义类型成员(且该类没有默认构造函数时)必须在初始化列表位置进行初始化。

  3. 尽量使用初始化列表初始化,因为不管你是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化。

  4. 成员变量在类中声明次序就是其在初始列表中的初始化顺序,与其在初始化列表中的先后次序无关。

1.2隐式类型转换

隐式类型转换总体可分为算数类型和类类型,这里主要讲类类型的隐式类型转换。如果一个类具有单参数构造函数,编译器会将该构造函数视为隐式转换操作符。这意味着,当需要该类类型的对象时,可以通过传递构造函数所需的参数来自动创建对象。

cpp 复制代码
class Date
{
public:
	// 1. 单参构造函数,具有类型转换作用
	Date(int year)
		:_year(year)
	{}
	// 2. 虽然有多个参数,但是创建对象时后两个参数可以不传递,具有类型转换作用
	Date(int year, int month = 1, int day = 1)
	: _year(year)
	, _month(month)
	, _day(day)
	{}
	Date& operator=(const Date& d)
	{
		if (this != &d)
		{
			_year = d._year;
			_month = d._month;
			_day = d._day;
		}
		return *this;
	}
private:
	int _year;
	int _month;
	int _day;
};
void Test()
{
	Date d1(2022);
    d1 = 2023;
    const Date& ref = 3;
}

用整形变量给类类型对象赋值,实际编译器会用2022创建一个无名对象,最后用无名对象进行赋值。在最后一行代码是一个引用,但是3是个常量值,所创建的对象具有常量性,所以必须加const进行修饰。

C++是强类型的编程语言,使用隐式类型转换,写代码更加方便,但是也存在着不小的风险。如果想要禁止构造函数的隐式类型转换,可以使用explicit关键字。

cpp 复制代码
class Complex {
public:
    double real, imag;

    explicit Complex(double r) : real(r), imag(0) {}
};

int main() {
    // Complex c1 = 3.5;  // 错误:由于构造函数被声明为 explicit,因此不能隐式转换
    Complex c2(3.5);     // 正确:显式调用构造函数
    return 0;
}

C++11后支持多参数的隐式类型转换。

cpp 复制代码
class Time {
public:
    Time(int hour = 8, int minute = 0, int second = 0) : m_hour(hour), m_minute(minute), m_second(second) {}
    void print() const {
        printf("%2d:%2d:%2d\n", m_hour, m_minute, m_second);
    }
private:
    int m_hour;
    int m_minute;
    int m_second;
};

void test2() {
    // C++11起支持多参数构造函数的隐式类型转换(大括号初始化)
    Time t = {9, 20, 0};
    t.print();
}

1.3匿名对象

在C++中,匿名对象指的是没有名字的对象。这些对象通常用于临时或一次性的场景,它们在创建后立即被使用,然后被销毁。

cpp 复制代码
class Complex {
public:
    double real, imag;
    Complex(double r) : real(r), imag(0) {}
};

int main() {
    Complex c2(3.5);//有名对象:生命周期在当前局部域
    Complex(2.2);//匿名对象:生命周期只在这一行
    return 0;
}

匿名对象有什么用呢?

  1. 调用成员函数,调用形式比较方便,但是只能调用一次 ,存在着一些局限性。如果你想要多次调用,还需要构造有名对象才行。

    cpp 复制代码
    class A
    {
    public:
        int print(int n)
        {
            cout << "print" <<end;
            return n;
        }
    };
    
    int main()
    {
        A a;
        a.print(1);
        print().print(1);//匿名对象调用
    }
  2. 匿名对象的引用。匿名对象和临时对象一样具有常性,所以需要用const修饰成常性的引用。

    cpp 复制代码
    A& a = A(1);//编译错误,权限放大问题。
    const A& a = A(1);//编译正确

    这里就存在着一个更深层的问题,匿名对象的生命周期只在这一行,这个引用会不会变成野引用 ?答案是不会 ,因为这个const常引用演唱了匿名对象的生命周期,会在程序结束时才进行销毁。

二、static成员

2.1静态成员的概念

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

2.2静态成员变量

静态成员变量,不能在类内部进行初始化,因为它不属于任何一个具体的对象,如果它在类的内部进行初始化,那么每个具体的对象被创建时,静态成员都被初始化一次,没有意义。所有对象都可以通过类名::静态成员 或者对象.静态成员进行访问。初始化时不需要添加static关键字,类中只是声明。

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

class My_class
{
public:
    static int my_static;
};

int My_class::my_static = 10;

int main()
{
    My_class a;
    cout << My_class::my_static << " " << a.my_static;
}

通过静态成员变量,计算程序中创建了多少个类对象。

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

class My_class
{
public:
    static int n;
    static int m;
    My_class()
    {
        ++m;
        ++n;
    }
    My_class(My_class const& a)
    {
        ++m;
        ++n;
    }
    ~My_class()
    {
        --m;
    }
};

int My_class::m = 0;
int My_class::n = 0;

int main()
{
    My_class a;
    My_class b(a);
    My_class();
    cout <<"当前存在对象个数为:" << My_class::m << " " << "创建对象总数为:" << My_class::n;
}

2.3静态成员函数

在C++中,静态成员函数也是属于类本身的,而不是类的任何特定对象。这意味着静态成员函数可以通过类名直接调用,而不需要创建类的实例。

静态成员函数只能访问类的静态成员变量和其他静态成员函数。它们不能访问非静态成员变量或非静态成员函数,因为这些成员需要一个具体的对象实例。

静态成员函数没有隐藏的this指针,因为它们不属于任何特定的对象。

静态成员函数可以在类的内部实现

cpp 复制代码
#include <iostream>

class My_Class {
public:
    static int count; // 声明静态成员变量

    static void printCount(); // 声明静态成员函数

    My_Class() {
        count++; // 每创建一个对象,count加1
    }
};

// 定义静态成员变量
int My_Class::count = 0;

// 定义静态成员函数
void My_Class::printCount() {
    std::cout << "已经创建的对象数量: " << count << std::endl;
}

int main() {
    // 通过类名调用静态成员函数
    My_Class::printCount();

    // 创建对象
    My_Class obj1;
    My_Class obj2;

    // 再次调用静态成员函数
    My_Class::printCount();

    return 0;
}

三、友元

3.1友元的概念

友元是一种允许一个类的私有和保护成员被另一个类或函数访问的机制。友元可以是函数或类。通过使用friend关键字,可以授予特定的函数或类对另一个类的私有和保护成员的访问权限

3.2友元函数

友元函数是可以访问类的私有和保护成员的非成员函数。友元函数的声明通常放在类的内部。友元函数是定义在类外部的不同函数,不属于任何类。

cpp 复制代码
#include <iostream>

class My_Class {
private:
    static int count; // 声明静态成员变量
public:
    My_Class() {
        count++; // 每创建一个对象,count加1
    }
    // 声明友元函数
    friend void displayCount();
};
// 定义静态成员变量
int My_Class::count = 0;

// 定义友元函数
void displayCount() {
    std::cout << "已经创建的对象数量(通过友元函数): " << My_Class::count << std::endl;
}

int main() {
    // 通过友元函数显示计数
    displayCount();

    // 创建对象
    My_Class obj1;
    My_Class obj2;

    // 再次通过友元函数显示计数
    displayCount();

    return 0;
}

说明:

  1. 友元函数可访问类的私有和保护成员,但不是类的成员函数
  2. 友元函数不能用const修饰
  3. 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
  4. 一个函数可以是多个类的友元函数。
  5. 友元函数与普通函数的调用原理相同。

3.3友元类

友元类是可以访问另一个类的私有和保护成员的类。友元类的所有成员函数都可以访问被授予友元关系的类的私有和保护成员。

cpp 复制代码
#include <iostream>

class My_Class {
private:
    static int count; // 声明静态成员变量

public:
    My_Class() {
        count++; // 每创建一个对象,count加1
    }

    // 声明友元类
    friend class FriendClass;
};

// 定义静态成员变量
int My_Class::count = 0;

// 定义友元类
class FriendClass {
public:
    void displayCount() const {
        std::cout << "已经创建的对象数量(通过友元类): " << My_Class::count << std::endl;
    }
};

int main() {
    // 创建友元类对象并调用其成员函数
    FriendClass friendObj;
    friendObj.displayCount();
    // 创建对象
    My_Class obj1;
    My_Class obj2;
    // 再次通过友元类对象调用其成员函数
    friendObj.displayCount();
    return 0;
}

说明:

  1. 友元关系是单向的,不具有交换性。比如说上述的FriendClass类和My_Class类,在My_Class类中声明FriendClass类为其友元类,那么可以在FriendClass类中直接访问My_Class类的私有成员变量,但在My_Class类中访问FriednClass中的私有成员变量则不行。
  2. **友元关系不能传递。**如果B是A的友元,C是B的友元,则不能说明C是A的友元。
  3. 友元关系不能继承,继承以后再讲。

四、内部类

4.1内部类的概念

在C++中,内部类(也称为嵌套类)是指定义在另一个类内部的类。内部类可以访问外部类的成员,包括私有成员,而外部类的成员函数则不能直接访问内部类的私有成员。外部类对内部类没有任何的优越的访问条件。

其实,内部类就是外部类的友元,但外部类不是内部类的友元类。

4.2内部类的特性

内部类可以定义在外部类的public、protected、private。

如果定义在public,则可以通过外部类名::内部类名来定义内部类的对象。

如果定义在private,则外部不可以定义内部类的对象

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

class A
{
private:
	int n;
public:
	A():n(10){}
	class B {
		int m;
		void print(const A& obj)
		{
			cout << obj.n;
		}
	};
};
int main()
{
	A obj;
	A::B obj2;
}

内部类可以直接访问外部类中的静态成员变量、枚举成员、不需要外部类的对象/类名。

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

class A
{
private:
	int n;
	static int k;
public:
	A():n(10){}
	class B {
		int m;
	public:
		void print()
		{
			cout << k;//ok
            //cout << h;//ERROR
		}
	};
};
int A::k = 3;
int main()
{
	A obj;
	A::B obj2;
	obj2.print();
}

直接在内部类中输出外部类的非静态成员变量是不妥的,因为内部类是一个独立的类,不属于外部类,所以此时还没有外部类的对象,也就不存在非静态的成员变量。但是对于静态的成员变量来说,不需要外部的对象就已经存在。

这里与友元类很相似,想要使用另一个类的成员,必须要存在这个类的对象。

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

class A
{
private:
	int n;
	static int k;
public:
	A() :n(10) {}
	class B {
		int m;
	public:
		void print(const A& obj)
		{
			cout << k<< endl;//ok
			cout << obj.n;//ok
		}
	};
};
int A::k = 3;
int main()
{
	A obj;
	A::B obj2;
	obj2.print(obj);
}

内部类可以在外部类中声明 ,然后在外部类外定义

cpp 复制代码
class A
{
private: 
    static int i;
public: 
    class B;
};
class A::B
{
public:
    void foo()
    {
        cout<<i<<endl;
    }
};
int A::i=3;
相关推荐
¥ 多多¥9 分钟前
c++中mystring运算符重载
开发语言·c++·算法
Mr.Pascal14 分钟前
刚学php序列化/反序列化遇到的坑(攻防世界:Web_php_unserialize)
开发语言·安全·web安全·php
小尤笔记26 分钟前
利用Python编写简单登录系统
开发语言·python·数据分析·python基础
秦老师Q29 分钟前
Java基础第九章-Java集合框架(超详细)!!!
java·开发语言
计算机毕设源码qq-383653104130 分钟前
(附项目源码)Java开发语言,215 springboot 大学生爱心互助代购网站,计算机毕设程序开发+文案(LW+PPT)
java·开发语言·spring boot·mysql·课程设计
无尽的大道41 分钟前
深入理解 Java 阻塞队列:使用场景、原理与性能优化
java·开发语言·性能优化
建群新人小猿1 小时前
会员等级经验问题
android·开发语言·前端·javascript·php
007php0071 小时前
GoZero 上传文件File到阿里云 OSS 报错及优化方案
服务器·开发语言·数据库·python·阿里云·架构·golang
数据小小爬虫1 小时前
如何利用Java爬虫获得1688店铺详情
java·开发语言
Tech Synapse1 小时前
Python网络爬虫实践案例:爬取猫眼电影Top100
开发语言·爬虫·python