Bootstrap

【C++】类和对象终章


请添加图片描述

前言

  前面,我们写了很多篇内容带大家了解类和对象的相关知识,大家可自行查看学习:

1️⃣【C++】类和对象
2️⃣ 【C++】类和对象——构造和析构函数
3️⃣ 【C++】类和对象——默认成员函数(下)
4️⃣ 【C++】类和对象——运算符重载之日期类的实现
5️⃣ 【C++】类和对象——流插入和流提取运算符重载

  下面,我们来将类和对象收个尾,即可进入下一阶段的学习。

explicit关键字

1.再谈构造函数

  弯弯绕绕,我们再次回到构造函数。对于构造函数,它不仅可以构造初始化对象,当它接受的是单个参数的构造函数时,还具有类型转换的作用
  接收单个参数的构造函数具体有以下几种:

  1. 构造函数只有一个参数。
  2. 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值。
  3. 是全缺省构造函数。

2.什么是类型转换

  到底什么是类型转换呢?来看下面这个例子:

int i = 10;
double d = i;

  我们想要把int类型的i转换为double型,这就是类型转换,那类型转换的过程是什么样的呢,如下所示:
在这里插入图片描述
  由图所示,i并没有直接赋值给了d,而是进行了隐式类型转换

  • 编译器会先创建一个double类型的临时变量,这个临时变量具有常性(const)
  • 先将i的值赋值给这个临时变量,临时变量再拷贝给给d。

如果说我们想要用引用来接收隐士类型转换后的值,则需写成:

const double& rd = i;

因为,rd引用的实际上是中间产生的临时变量,临时变量具有常性,所以需要加上const,否则就是const权限的扩大,会报错。

3.单参数构造函数

  只传一个参数的构造函数叫单参数构造函数。
  举个例子:

class A
{
public:
	A(int a)
		:_a(a)
	{
		cout << "A(int a)" << endl;
	}
	A(const A& a)
	{
		_a = a._a;
		cout << "A(const A& a)" << endl;
	}
	void Print()
	{
		cout << _a << endl;
	}
private:
	int _a;
};

int main()
{
	A aa1(1);//构造
	aa1.Print();
	A aa2 = aa1;//拷贝构造
	aa2.Print();
	A aa3 = 3;//隐式类型转换
	aa3.Print();
	return 0;
}

结果如下:
在这里插入图片描述

  对于A这个类,我们对它写了构造,拷贝构造和打印三个成员函数。我们在main函数中创建了三个对象:

  • aa1是直接构造得来的
  • aa2是拷贝构造来的
  • 对于aa3我们是想要一个int类型的整数赋值给自定义类型,这中间要通过隐式类型转换。通过运行结果我们发现它只存在一个直接构造,这是为什么呢。
    在这里插入图片描述

  由图,对于隐式类型转换,都会产生一个临时对象,当3想由内置类型转换为自定义类型,会先用3构造一个A的临时对象,用这个临时对象拷贝构造aa3,但是编译器遇到构造和拷贝构造会将其优化为直接构造,这也是我们看到结果只有直接构造的原因。
  如果想要使用引用,同样的要在前面加const,如下:

const A& raa = 3;
//raa引用的是类型转换中用3构造的临时对象。

应用:我们在写Stack这个类时,通常这么写:

class Stack
{
    void Push(const A& aa)//我想Push一个A类型的数据
    {
       //....
    }
    //...
};
int main()
{
    Stack st;
    
    A a1(1);//先构造一个A类型的a1对象,再传参
    st.Push(a1);//自定义类型传参要先调用拷贝构造

    st.Push(2);//直接传,会进行隐式类型转换,将int型转换为A类型
}

  有了隐式类型转换,我们可以少写一行代码,且减少了一次拷贝构造,代码更优。

explicit关键字

  explicit关键字的作用此时就来了,单参数构造函数没有用 explicit关键字修饰,具有类型转换的作用。
  如果在其前面使用了 explicit关键字,则会禁止类型转换。

	explicit A(int a)//加了explicit关键字,禁止类型转换
   	:_a(a)
   {
   	cout << "A(int a)" << endl;
   }

还是原来的代码,此时编译就不能通过:

error C2440: “初始化”: 无法从“int”转换为“A”

  因此,explicit关键字的作用就是:用其修饰构造函数,会禁止构造函数的隐式转换。

多参数构造函数

  多参数构造函数和单参数构造函数类似同样支持隐式类型转换:

class A
{
public:
    A(int a1, int a2)
    :_a1(a1)
    ,_a2(a2)
    {}
private:
    int _a1;
    int _a2;
};

int main()
{
    const A& aa1 = {1, 2};
    //多参数用花括号括起来即可

    Stack st;
    st.Push({1, 2});//同样可以直接传参,会进行隐式类型转换
}

static成员

概念

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

特性

  1. 静态成员变量为所有类所共享,不属于某个具体的类,存放在静态区
class A
{
private:
	int _a;
	static int _count;
};

int main()
{
	cout << sizeof(A) << endl;//结果为4
	return 0;
}

  A对象中有一个普通成员变量,有一个静态成员变量,我们想要计算A的大小,结果为4,并不是我们想象中的8,也就验证了静态成员变量不属于某个具体的类。


  1. 静态成员变量必须在类外定义,定义时不用添加static关键字,类中只是声明。
class A
{
private:
	int _a;
	static int _count;
};
int A:: _count = 0;//在类外定义,类似函数的声明和定义

注意不能给静态成员变量赋缺省值,因为缺省值是给初始化列表使用的,初始化列表式给对象中的成员变量初始化,但它并不在对象中,它在静态区。


  1. 类静态成员可以用类名::静态成员或者对象.静态成员来访问。
class A
{
public:
	static int GetACount()//静态成员函数
	{
		return _count;
	}
private:
	int _a;
	static int _count;
};
int A:: _count = 0;

int main()
{
	cout << sizeof(A) << endl;
	cout << A::GetACount() << endl;
	A a;
	cout << a.GetACount() << endl;//两种访问方式都可以
	return 0;
}

  1. 静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
static int GetACount()
{
	return _count;
	return _a;//静态成员函数访问了非静态成员变量,报错
}

上述代码会报错:
error C2597: 对非静态成员“A::_a”的非法引用


  1. 静态成员也是类的成员,受public、protected、private访问限定符的限制。

注意
静态成员函数不可以调用非静态成员变量
非静态成员函数可以调用静态成员变量

例题1:计算程序中创建了几个类对象

我们想要实现一个类,统计对象被创建了多少个,我们知道,对象不是被构造出来的,就是拷贝构造出来的,因此我们只要定义一个静态成员变量_count,在构造和拷贝构造时对其进行++,输出即可,代码如下:

class A
{
public:
	A()
	{
		++_count;
	}
	A(const A& a)
	{
		++_count;
	}
	~A()
	{
		--_count;
	}
	static int GetACount()
	{
		return _count;
	}
private:
	int _a;
	static int _count;
};
int A:: _count = 0;

int main()
{
	cout << A::GetACount() << endl;
	A a1, a2;
	A a3(a2);
	cout << A::GetACount() << endl;
	return 0;
}

结果则为3。

例题2:1+2+……+n

题目链接:1+2+……+n

题目要求:求1+2+3+…+n,要求不能使用乘除法、for、while、if、else、switch、case等关键字及条件判断语句(A?B:C)。

这道题我们就可以利用static的特性,建立两个静态成员变量_sum,_count,每调用一次构造函数,就让_count++,并让_sum+=_count,即可实现,代码如下:

class Count{
public:
    Count()
    {
        _sum += _count;
        _count++;
    }
    static int GetSum()
    {
        return _sum;
    }
private:
    static int _count;
    static int _sum;
};
int Count::_count = 1;
int Count::_sum = 0;

class Solution {
public:
    int Sum_Solution(int n) {
        Count a[n];
        return Count::GetSum();
    }
};

友元

  友元分为了友元函数友元类
  友元提供了一种突破封装的方式,有时提供了便利。但是它破坏了封装,所以友元不宜多用

友元函数

  在写日期类时我们就有提到过,对于流提取和流插入运算符的重载只能将其重载为全局函数,但是这样就不能访问私有成员了。此时我们就用到友元函数,如下:

class Date
{
    //对友元函数的声明,声明在public或者private里面都行,习惯声明在最前面
    friend ostream& operator<<(ostream& out, const Date& d);
    friend istream& operator>>(istream& in, Date& d);
private:
    int _year;
    int _month;
    int _day;
};
    ostream& operator<<(ostream& out, const Date& d);
    istream& operator>>(istream& in, Date& d);//定义

  友元函数可以直接访问类的私有成员,它是在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加上friend关键字。

  • 友元函数不能用const修饰
  • 一个函数可以是多个类的友元函数
  • 友元函数的调用与普通函数调用相同

友元类

  友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。

class Time
{
    friend class Date;
    //声明Date类是Time类的友元类,在Date类中可以直接访问Time类中的私有成员
private:
    int _hour;
    int _minute;
    int _sec;
};
class Date
{
public:
    //直接访问时间类中的私有成员变量
    void SetTimeOfDate(int hour, int minute, int sec)
    {
        _t._hour = hour;
        _t._minute = minute;
        _t._sec = sec; 
    }
private:
    int _year;
    int _month;
    int _sec;
    Time _t;
};

可以记为:日期类是时间类的朋友,所以日期类可以去时间类家做客。
说明:

  • 友元关系是单向的,不具有交换性。
    如上述代码,在Time类中声明了Date类为友元类,则在Date类中可以直接访问Time类,但是不能在Time类中访问Date类的私有成员变量。
  • 友元关系不能传递
    如果B是A的友元,C是B的友元,但不能说明C是A的友元。
  • 友元关系不能继承

内部类

概念

  如果一个类定义在另一个类的内部,这个内部的类就叫内部类。
  内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。外部类对内部类没有任何优越的访问权限。

注意内部类就是外部类的友元类,内部类就可以访问外部类的私有成员变量。

class A
{
private:
    static int k;
    int h;
public:
    class B
    {
    public:
        void foo(const A& a)
        {//B天生就是A的友元
            cout << k << endl;//正确
            cout << a.h << endl;//正确
        }
    };
};
int A::k = 1;

int main()
{
    A::B b;
    A a;
    b.foo(a);
    return 0;
}

特性

  • 内部类定义在外部类的任意位置都可以。
  • 内部类可以直接访问外部类的stctic成员,不需要外部类的对象/类名。
  • sizeof(外部类) = 外部类,和内部类没有任何关系
  • 外部类和内部类是平行关系,内部类仅受到类域的限制

匿名对象

  该对象没有名字就叫匿名对象。
  它的特点是:声明周期只存在当前这一行。

A aa1;//有名对象
A aa2(1);

A(2);//匿名对象,声明周期只在这一行

  对于类和对象的探索远还没有结束,经过后面的学习,我们对类和对象会有更深入的了解,这一阶段的类和对象的学习就到此结束。感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。
请添加图片描述

;