目录
explicit关键字
static成员
static成员的介绍
static成员的使用
友元
友元函数
友元类
内部类
匿名对象
explicit关键字
在C++中,给类对象初始化时会调用类的构造函数,但是也可以使用赋值运算符为构造函数只有一个参数(或者只有一个参数没有缺省值)的类对象赋值,如下面代码
#include <iostream>
using namespace std;class test
{
private:int _num;
public:test(int num):_num(num){}void print(){cout << _num << endl;}
};int main()
{test t(1);//直接调用构造函数进行对象实例化test t1 = 1;t.print();t1.print();return 0;
}
输出结果:
1
1
在上面的代码中,test
类对象初始化时需要调用有一个参数的构造函数(对应test t(1)
),而也可以直接使用赋值运算符,将初始化值赋值给类对象,这个过程经历了:调用构造函数使用整型1为临时对象初始化,再调用拷贝构造函数将临时对象拷贝给t1
对象,这个过程也是一种类型转换,但是实际上这个过程一般会被编译器优化为直接调用构造函数,使用整型1对类对象初始化,即优化过程:构造函数+拷贝构造函数->构造函数
而如果不想以上面的方式,只用直接调用构造函数的方式对类对象进行初始化时,可以使用explicit
关键字对构造函数进行修饰
#include <iostream>
using namespace std;class test
{
private:int _num;
public:explicit test(int num):_num(num){}void print(){cout << _num << endl;}
};int main()
{test t(1);//直接调用构造函数进行对象实例化test t1 = 1;t.print();t1.print();return 0;
}
报错信息:
不存在从 "int" 转换到 "test" 的适当构造函数
当构造函数被explicit
关键字修饰后,test t1 = 1
的初始化方式失效
在C++11标准规范中,也支持对不只有一个参数的构造函数使用对类对象进行赋值初始化的方式
#include <iostream>
using namespace std;class test
{
private:int _num;int _num1;
public:test(int num, int num1):_num(num),_num1(num1){}void print(){cout << _num << " " << _num1 << endl;}
};int main()
{test t(2, 3);test t1 = { 2,4 };t.print();t1.print();return 0;
}
输出结果:
2 3
2 4
同样,如果不愿意使用直接赋值的方式为类对象进行初始化时,可以使用explicit
关键字修饰构造函数
#include <iostream>
using namespace std;class test
{
private:int _num;int _num1;
public:explicit test(int num, int num1):_num(num),_num1(num1){}void print(){cout << _num << " " << _num1 << endl;}
};int main()
{test t(2, 3);test t1 = { 2,4 };t.print();t1.print();return 0;
}
报错信息:
"test" 的复制列表初始化不能使用显式构造函数
但是,如果为已经实例化的对象再次赋值时,则会调用赋值运算符重载函数为对象赋值,如下面代码
#include <iostream>
using namespace std;class test
{
private:int _num;
public:test(int num):_num(num){}void print(){cout << _num << endl;}test(const test& d){_num = d._num;}test& operator=(const test& t){if (this != &t){_num = t._num;}return *this;}
};int main()
{test t(1);//为对象初始化t = 2;return 0;
}
在上面的代码中,t
对象再次赋值时会调用赋值重载,首先会调用拷贝构造函数,但是会被编译器优化为直接调用构造函数,再调用赋值重载函数
static成员
static成员的介绍
当需要统计一个类创建了多少个对象时,第一反应是创建一个全局变量,当每一次调用构造函数或者拷贝构造函数时,就让其进行+1操作,但是这个思路的问题是该全局变量不仅是在类中可以访问,也可以在类外访问,此时如果在类外改变了该变量的值,那么此时的计数不一定准确,如果直接放置到类内作为类的成员变量,那么每一个类对象在创建时都会为这个成员变量分配独立的空间,那么每一个类对象的计数器变量都只为1,并没有达到计数的效果
为了解决上面的问题,C++支持在类中创建静态成员变量,该成员变量不属于任何一个实例对象,而属于整个类,但是因为在类内,所以类中的成员函数也可以直接访问该静态成员变量。一样的思路,当每一次调用构造函数或者拷贝构造函数时让该变量+1即可实现统计一个类有多少个对象,并且这个方法的好处是保证了类的封装性,如果该成员变量具有private
属性,那么在类外也不可以直接修改(要修改时需要类中提供静态set
成员函数),但是此时要在类外直接访问该静态成员变量时需要提供get
函数,如下面的代码
#include <iostream>
using namespace std;class test
{
private://静态成员变量static int _count;//静态成员变量声明
public:test(){_count++;}test(const test& t){_count++;}//使用静态成员函数在类外访问静态成员变量static int getCount(){return _count;}
};int test::_count = 0;//静态成员变量初始化int main()
{test t;test t1;test t3(t1);//统计创建的对象的个数cout << test::getCount() << endl;return 0;
}
输出结果:
3
上面代码的作用是统计创建类对象的个数,基本思路是通过统计调用构造函数或者拷贝构造函数的次数来统计类对象的个数,类中存在一个静态成员变量_count
,该成员变量从属于整个类而不是某一个单独的类对象,因为是静态成员变量,所以需要使用静态成员函数才可以在类域外访问(因为具有private
属性,所以不可以直接通过类名进行调用)
static成员的使用
在C++中,声明为static
的类成员称为类的静态成员,用static
修饰的成员变量,称之为静态成员变量
同样,用static
修饰的成员函数,称之为静态成员函数。
📌
注意:静态成员变量一定要在类外进行初始化
static
成员的特点
- 静态成员为所有类对象所共享,不属于某个具体的对象,存放在静态区,在使用
sizeof
计算类大小时同样不会包括static
成员的大小 - 静态成员变量必须在类外定义,定义时不添加
static
关键字,类中只是声明 - 类静态成员即可用 类名
::
静态成员 或者对象.静态成员
来访问(前提是非private
属性) - 静态成员函数没有隐藏的
this
指针,不能访问任何非静态成员 - 静态成员也是类的成员,受
public
、protected
、private
访问限定符的限制
#include <iostream>
using namespace std;class test
{
private:int _num;//非静态成员变量static int _count;//静态成员变量
public:test(int num):_num(num){}static int getCount(){_num = 1;//无法访问非静态成员变量return _count;}
};int main()
{test t(1);return 0;
}
报错信息:
非静态成员引用必须与特定对象相对
友元
在C++中,友元提供了一种突破封装的方式,有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。
友元分为:友元函数和友元类
友元函数
在默认构造函数章节中,对流插入运算符和流提取运算符进行重载时,如果将这两个运算符重载函数放置在类中时,那么ostream
和istream
的对象将与this
指针抢占第一个参数的位置,导致最后调用时和正常的输出输入参数相反,而解决这个问题时考虑到将这两个重载函数放置到全局,但是放置到全局时将无法看到类成员变量,此时为了同时解决前面两个问题,考虑使用友元函数,如下面代码
#include <iostream>
using namespace std;class test
{
private:int _num;
public:test():_num(){}//友元函数声明//友元函数声明可以放在任意位置friend ostream& operator<<(ostream& cout, const test& d);friend istream& operator>>(istream& cin, test& t);
};//重载流插入运算符
ostream& operator<<(ostream& cout, const test& t)
{cout << t._num;return cout;
}//重载流提取运算符
istream& operator>>(istream& cin, test& t)
{cin >> t._num;return cin;
}int main()
{test t;cin >> t;cout << t;return 0;
}
输入:
2
输出结果:
2
友元函数可以直接访问类的私有成员,它是定义在类外部的普通函数,不属于任何类,但需要在类的内部声明,声明时需要加friend
关键字
友元函数的特点:
- 友元函数可访问类的私有和保护成员,但不是类的成员函数
- 友元函数不能用
const
修饰 - 友元函数可以在类定义的任何地方声明,不受类访问限定符限制
- 一个函数可以是多个类的友元函数
- 友元函数的调用与普通函数的调用原理相同
友元类
友元类的所有成员函数都可以是另一个类的友元函数,都可以访问另一个类中的非公有成员。
友元类的特点:
- 友元关系是单向的,不具有交换性。
- 友元关系不能传递
💡
若C是B的友元类(即C想使用B类中的成员),B是A的友元类(即B想使用A类中的成员),不能推出C是A的友元类(即C依旧无法使用A中的成员)
- 友元关系不能继承(见继承章节)
#include <iostream>
using namespace std;class B
{
private:int _numB;friend class A;//声明A是B的友元类
public:B():_numB(2){}
};class A
{
private:int _numA;B _b;
public:A():_numA(){}void print(){cout << _b._numB << endl;}
};int main()
{A a;a.print();return 0;
}
输出结果:
2
在上面的代码中,定义了两个类A和B,在类B中声明了类A为B的友元类,则A类中可以访问B中的成员
📌
注意,尽管A类是B类的友元类,但是B类不可以访问A类中的成员,即单向传递
💡
友元类的声明规则:当某个类想使用另一个类中的成员时,就在另一个类中声明友元类,例如在上面的代码中,A类想使用B类的成员,则A类在B类声明友元类(即想用哪一个类就在哪个类声明友元类)
内部类
如果一个类定义在另一个类的内部,这个内部类就叫做内部类。内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象去访问内部类的成员。
📌
内部类就是外部类的友元类,内部类可以通过外部类的对象参数来访问外部类中的所有成员。但是外部类不是内部类的友元,即外部类不可以访问内部类成员
内部类特点:
- 内部类可以定义在外部类的
public
、protected
、private
都是可以的(若内部类具有private属性,则不能使用外部类::内部类 对象名
的方式创建对象) - 注意内部类可以直接访问外部类中的
static
成员,不需要外部类的对象/类名 sizeof(外部类)=外部类
,和内部类没有任何关系- 内部类虽然与外部类独立,但是内部类的生命周期还是外部类域
#include <iostream>
using namespace std;class outer
{
private:int _num;static int _num1;public:outer():_num(){}class inner{private:int _num2;public:void print(const outer& o){cout << o._num << endl;//非静态成员需要外部类对象cout << _num1 << endl;//静态成员可以不使用外部类对象}};
};int outer::_num1 = 2;int main()
{outer o;outer::inner i;i.print(o);return 0;
}
输出结果:
0
2
在上面的代码中,外部类outer
中有一个inner
内部类,该内部类中可以通过外部类的对象o访问外部类的成员,而对于外部类的静态成员则可以直接访问
📌
注意,内部类中不可以创建外部类对象
#include <iostream>
using namespace std;class outer
{
private:int _num;static int _num1;
public:outer():_num(){}class inner{private:int _num2;public:void print(const outer& o){cout << o._num << endl;//非静态成员需要外部类对象cout << _num1 << endl;//静态成员可以不使用外部类对象}};
};int outer::_num1 = 2;int main()
{outer o;outer::inner i;i.print(o);return 0;
}
报错信息:
“outer::inner::_o”使用未定义的 class“outer”
因为内部类和外部类是两个单独的类,所以在使用sizeof
计算外部类大小时不会包括内部类的大小
#include <iostream>
using namespace std;class outer
{
private:int _num;static int _num1;
public:outer():_num(){}class inner{private:int _num2;};
};int outer::_num1 = 2;int main()
{cout << sizeof(outer) << endl;return 0;
}
输出结果:
4
匿名对象
所谓匿名对象,即类对象在实例化时不给名称
#include <iostream>
using namespace std;class test
{
private:int _num;
public:test(int num):_num(num){}void print(){cout << _num << endl;}
};int main()
{//普通对象test t(1);t.print();//匿名对象test(2).print();return 0;
}
输出结果:
1
2
在上面的代码中,创建了两个对象,一个对象是普通对象,普通对象的生命周期为当前函数栈帧空间销毁之前,另外一个对象是匿名对象,匿名对象的生命周期为所在行,当匿名对象执行完所在行之后就会被销毁
📌
注意:匿名对象与常量一样,当给自定义类型的引用变量时需要有const