explicit关键字
我们先来看看这段代码:
#include<iostream>
using namespace std;
class A
{
public:
A(int a)
:_a(a)
{
cout << _a << endl;
};//构造函数
private:
int _a;
};
int main()
{
A a(1);
A b = 2;
return 0;
}
这里我们发现这两个都是拷贝构造,有两种实例化的方式,
但其实内部是不一样的,
第一个是把1传递给a的构造函数中,之后再把a这个对象中的成员变量赋值成1。
第二个是将1隐式类型转换成A类型,然后拷贝给b这个对象。
如果不想发生隐式类型转换就可以用explicit这个关键字来修饰,只需要在该类的构造函数前面加上explicit:

另外还补充一点:
上面这个例子是对于单个参数,也就是参数列表中只有一个参数,那对于多个参数呢?
C++98不支持,在C++11才支持。
它的实例化这样的:
#include<iostream>
using namespace std;
class A
{
public:
A(int a,int b)
:_a(a)
,_b(b)
{
cout << _a << _b << endl;
};//构造函数
private:
int _a;
int _b;
};
int main()
{
/*A a(1);
A b = 2;*/
A arr(1, 2);
A array = { 2,3 };
return 0;
}
Static成员变量
在学C语言时我们就知道被static修饰的变量叫静态变量,那么在类中,被static修饰的成员就叫静态成员,其中,如果是变量被修饰,那就是静态成员变量,如果是函数被修饰,那就是静态成员函数。
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;
void TestA()
{
cout << A::GetACount() << endl;
A a1, a2;
A a3(a1);
cout << A::GetACount() << endl;
}
静态成员变量不能在类中定义,必须在全局定义(初始化)。
静态成员的特性:
静态成员不是单独属于某个对象,所有用这个类实例化出来的对象都可以共享,静态成员是存在静态区的。
静态成员变量必须在类的外面定义初始化,并且定义时不用加static,在类中它只是声明。
访问静态成员变量可以用类名::静态成员变量,或者对象.静态成员变量,当然,这两个方法有个前提:就是该静态成员的访问限定符是公开的(public),所以,静态成员变量它是受访问限定符约束的。
另外,再说一说为什么要在成员函数前面加上static?
以上面代码为例,先假设static已经去掉了,这个代码是在统计一共创建了多少个对象,在TestA函数中创建了3个对象,那么在main函数中调用这个函数后,还要调用GetACount()成员函数来打印共创建了多少个对象,那么此时必须要先创建一个对象来专门访问这个成员函数的,这样就有点浪费空间并且没必要,所以我们可以在成员函数前面加上static修饰之后,就可以直接用类名::静态成员函数进行访问。
静态成员函数参数没有this指针,所以,静态成员函数不能访问类成员变量,但是成员函数可以访问静态成员变量。
接下来我们来试试这道题:点击这里
这里我们就可以通过之前那段代码找到灵感,先声明一个被static修饰的成员变量来进行递增,把它初始化为1,再声明一个被static修饰的成员变量来算总和,把它初始化为0,每次前面这个变量递增一次,就加上这个递增后的结果。
代码如下:
#include <climits>
class Sum
{
public:
Sum()
{
count += i;
i++;
}
static int Getsum()
{
return count;
}
private:
static int count;
static int i;
};
int Sum::count = 0;
int Sum::i = 1;
class Solution {
public:
int Sum_Solution(int n) {
Sum arr[n];
return Sum::Getsum();
}
};
友元
在类中,被private修饰的类成员会被封装在一起不能在外部直接访问,被public修饰的就可以在外部直接使用,而友元就可以直接访问被private修饰的类成员,突破这种封装性,所以不建议多用。
友元函数
先看一段代码:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d(2023, 4, 2);
int a = 2;
cout << a << endl;
cout << d << endl;
return 0;
}
在编译器中就会发现一个报错:

这个时候我们就要用运算符重载重载一个日期类的流插入,先看看标准库里是什么样的
网站链接:https://legacy.cplusplus.com/reference/

上图中,ostream,istream是类,cout其实是ostream这个类实例化出来的全局对象,cin是istream这个类实例化出来的全局对象,然后我们点进ostream这个类里面去看看,往下翻找到operator<<

点进去就可以看到有这么多的声明:

红框里的全是内置类型,看到这里我们也就能明白为什么在C++中输入输出不需要格式化自动识别类型了,因为构成函数重载,不同类型会直接去调用最匹配的函数。
接下来,我们试着写一下流插入的运算符重载
void operator<<(ostream& out)//在类里面有隐含的this指针
{
out << _year << "年" << _month << "月" << _day << "日" << endl;
}
但是这么写的话,我们那怎么用这个运算符重载呢?
int main()
{
Date d(2023, 4, 2);
int a = 2;
//cout << a << endl;
//cout << d << endl;
d << cout; //等同于d.operator<<(cout);
cout << i << endl;
return 0;
}
这样写是不是觉得很别扭,自定义类型和内置类型的写法不一样,我们来分析分析为什么?
首先我们先捋清楚,运算符重载参数第一个参数是左操作数,第二个参数是右操作数,
再根据库里的operator<<的参数列表,以上面代码中内置类型int i变量为例,cout是一个ostream类的对象,作为this指针传递,所以它是第一个操作数,而右操作数就是i这个变量,
然后调用最匹配int类型的函数,从而输出。
再回到我们自己写的函数中,this指针是日期类对象,是第一个参数,而cout是第二个参数,所以写的话日期类对象自然就在左边,cout就在右边,
那怎么样写可以反过来呢,换句话说就是怎么样让cout作为第一个参数,日期类对象作为第二个参数?我们可以写在全局中:
void operator<<(const Date& tmp,ostream& out)
{
out << tmp._year << "年" << tmp._month << "月" << tmp._day << "日" << endl;
}
此时,编译器又会弹出新的报错,报错指出:成员函数不可访问,它被访问限定符限制。
这个时候友元函数就派上用场了,我们只需要在类里面加上下面这句代码:
friend void operator<<(const Date& tmp, ostream& out);
这句话的意思就是表示这个函数是这个类的友元函数,友元函数可以访问私有的成员。
另外,这个函数还有点瑕疵:不能连续插入
int main()
{
Date d(2023, 4, 2);
int a = 2;
cout << a << endl;
cout << d << endl;//此时就不行了
return 0;
}
而且,再往上翻看看就会发现,标准库里的是有返回值的,那返回的是什么呢?返回的是osteam类的对象cout,所以这个函数还得再改一改:
ostream& operator<<(ostream& out, const Date& tmp)
{
out << tmp._year << "年" << tmp._month << "月" << tmp._day << "日" << endl;
return out;
}
另外在类里面友元函数的声明也改一下:
friend ostream& operator<<(ostream& out, const Date& tmp);
根据上面的情况,我们不难看出:
友元函数可以访问类的私有和保护成员,但不属于这个类
友元函数和普通的自定义函数在调用上没什么区别,
另外还有两点:
一个函数可以是多个类的友元函数,
友元函数不能被const修饰。
友元类
class Time
{
friend class Date;
public:
Time(int hour = 0, int minute = 0, int second = 0)
: _hour(hour)
, _minute(minute)
, _second(second)
{}
private:
int _hour;
int _minute;
int _second;
};
class Date
{
public:
Date(int year = 1900, int month = 1, int day = 1)
: _year(year)
, _month(month)
, _day(day)
{}
void SetTimeOfDate(int hour, int minute, int second)
{
_t._hour = hour;
_t._minute = minute;
_t._second = second;
}
private:
int _year;
int _month;
int _day;
Time _t;
};
友元类的声明和友元函数差不多,上面代码中Date类是Time类的友元类,所以在Date类中可以直接访问Time中的成员(同样不受访问限定符的限制),
但是!友元类不是双向的,就是说Date类中可以访问Time类成员,但是Time类中不能访问Date类成员。
内部类
class A
{
public:
class B
{
public:
B(int b_tmp = 2)
:b(b_tmp){}//构造
void Print(const A& a)
{
cout << x << endl;
cout << a.a << endl;
}
private:
int b;
};
A(int a_tmp = 1)
:a(a_tmp){}//构造
private:
static int x;
int a;
};
int A::x = 1;
B这个类写在A这个类的里面,那么B这个类就是A的内部类,
B这个类是独立的,不属于A这个类,我们可以用一段代码来看出来为什么?
int main()
{
cout << sizeof(A) << endl;
return 0;
}
结果如下:

所以B这个类不属于A这个类里面,只不过A类的作用域是全局的,而B类的作用域是仅限于A里面,也就是说在外部不能直接用B类创建对象,必须通过域操作符在A类中找到B类。
A类实例化出来的对象不能访问B里的成员,但是B类可以访问A类的成员,换句话说:B这个类天生就是A这个类的友元类,可以看下面这段代码:
class A
{
public:
class B
{
public:
B(int b_tmp = 2)
:b(b_tmp){}//构造
void Print(const A& a)
{
cout << x << endl;
cout << a.a << endl;
}
private:
int b;
};
A(int a_tmp = 1)
:a(a_tmp){}//构造
private:
static int x;
int a;
};
int A::x = 1;
int main()
{
A::B a;
a.Print(A());
return 0;
}
匿名对象
class Sum
{
public:
Sum()
{
count++;
}
Sum(const Sum& t) { ++count; }
static int Getsum()
{
return count;
}
private:
static int count;
};
int Sum::count = 0;
void func()
{
Sum s;
Sum s1;
Sum tmp(s1);
}
int main()
{
func();
//怎么知道一共创建过多少个对象
return 0;
}
上面这个代码其实就是本文中Static成员内容中的一段代码改了一下,它是统计共创建过多少个对象,现在我们在一个函数中创建对象,然后在main函数中调用该函数,再在main函数里打印结果,怎么打印呢?
除了直接在类里面用与操作符访问,还有另一种方法,就是使用匿名对象,格式如下:
cout << Sum().Getsum << endl;//其中,Sum()相当于创建了一个匿名对象
如果你只是想访问一下类里的成员,访问完之后就不用了,后续如果还有访问成员的需求再写,此时就可以使用匿名对象,匿名对象的生命周期只在当前行,下一行就会自动调用这个类的析构函数,相当于一次性用品。
文章详细介绍了C++编程中的explicit关键字用于防止隐式类型转换,静态成员变量和静态成员函数的概念及用法,包括它们的初始化和访问方式。接着讨论了友元函数和友元类,解释了如何打破封装并允许访问私有和保护成员。最后,文章提到了内部类(嵌套类)以及匿名对象的应用,展示了如何在不需要长期存在的情况下短暂使用对象。
1156

被折叠的 条评论
为什么被折叠?



