目录
前言
前面,我们写了很多篇内容带大家了解类和对象的相关知识,大家可自行查看学习:
1️⃣【C++】类和对象
2️⃣ 【C++】类和对象——构造和析构函数
3️⃣ 【C++】类和对象——默认成员函数(下)
4️⃣ 【C++】类和对象——运算符重载之日期类的实现
5️⃣ 【C++】类和对象——流插入和流提取运算符重载
下面,我们来将类和对象收个尾,即可进入下一阶段的学习。
explicit关键字
1.再谈构造函数
弯弯绕绕,我们再次回到构造函数
。对于构造函数,它不仅可以构造初始化对象
,当它接受的是单个参数的构造函数时,还具有类型转换的作用
。
接收单个参数的构造函数具体有以下几种:
- 构造函数只有一个参数。
- 构造函数有多个参数,除第一个参数没有默认值外,其余参数都有默认值。
- 是全缺省构造函数。
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修饰的成员函数,称之为静态成员函数。
特性
- 静态成员变量为所有类所共享,不属于某个具体的类,
存放在静态区
。
class A
{
private:
int _a;
static int _count;
};
int main()
{
cout << sizeof(A) << endl;//结果为4
return 0;
}
A对象中有一个普通成员变量,有一个静态成员变量,我们想要计算A的大小,结果为4,并不是我们想象中的8,也就验证了静态成员变量不属于某个具体的类。
- 静态成员变量必须在类外定义,定义时不用添加static关键字,类中只是声明。
class A
{
private:
int _a;
static int _count;
};
int A:: _count = 0;//在类外定义,类似函数的声明和定义
注意:不能给静态成员变量赋缺省值,因为缺省值是给初始化列表使用的,初始化列表式给对象中的成员变量初始化,但它并不在对象中,它在静态区。
- 类静态成员可以用类名::静态成员或者对象.静态成员来访问。
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;
}
静态成员函数没有隐藏的this指针,不能访问任何非静态成员。
static int GetACount()
{
return _count;
return _a;//静态成员函数访问了非静态成员变量,报错
}
上述代码会报错:
error C2597: 对非静态成员“A::_a”的非法引用
- 静态成员也是类的成员,受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);//匿名对象,声明周期只在这一行
对于类和对象的探索远还没有结束,经过后面的学习,我们对类和对象会有更深入的了解,这一阶段的类和对象的学习就到此结束。感谢大家观看,如果大家喜欢,希望大家一键三连支持一下,如有表述不正确,也欢迎大家批评指正。