前提提示:
其实C++中的类和对象,最重要的是6个默认函数,如有忘记,请移步到:类和对象。本章是对于一些细节知识的补充和拓展。
1. 隐示类型转换
在运算时,运算符左右两边的操作数的类型不同,编译器是会按照类型转换的要求,将两个操作数的类型转换为一样,然后就行运算。
1.1 内置类型的隐示类型转换
我们的内置类型,他们之间出现类型不一致的情况时,是有一套类型转换的规则的,但是还有自定义类型,内置类型和自定义类型之间是如何转化?
答:这个其实和我们的前面将的构造函数有关了,其实在类型转换的时候,我们可以很容易的发现,被类型转化的变量其实没有改变,但是确确实实有实现了运算,就是因为隐示类型转换的时候,产生了临时变量,临时变量存储着变量类型转换之后的结果。
#include<iostream>
using namespace std;
int main()
{
double b = 1.223;
int a = b;
cout << a << " " << b << endl;
return 0;
}
程序的执行逻辑:
这串代码实际上就是可以反映出,在隐示类型转换时,产生了临时变量。如果不产生临时变量,那么输出的结果是:1 1,但输出的结果是1 1.223 ,就说明产生了临时变量。
1.2 自定义类型的隐示类型转换
解释:这里的自定义类型的隐示类型转换,是指内置类型想自定义类型转换,自定义类型是无法想内置类型转换的。同时不同的自定义类型之间也是不能互相类型转换的。
#include<iostream>
using namespace std;
class A
{
public:
//构造函数
A(int a)
: x(a),
y(a)
{
cout << "A(int a)" << endl;
};
void operator=(A&a)
{
x = a.x;
y = a.y;
cout << "void operator=(A&a)" << endl;
}
private:
int x;
int y;
};
int main()
{
int a = 6;
A tmp = a;
return 0;
}
上述程序中的两个变量 a和tmp的类型不相同,那他们是怎么进行隐示类型转换的呢?这个就需要和之前的初始化列表联系起来了,因为这个转换需要使用到类的6个默认函数中的连续构造函数和拷贝构造,程序的执行逻辑如下。
程序输出的结果:
为什么上面讲的是先连续构造然后拷贝构造,为什么程序在执行的时候只进行了普通的构造?这是因为编译器的优化。编译器将连续构造+拷贝构造优化为直接构造,这样更加的节省空间和时间。但是我们也发现其实这样的隐示类型转换时十分依赖于构造函数的书写的。
当将构造函数修改为如下:
class A
{
public:
//构造函数
A(int a=0,int b=5)
: x(b),
y(b)
{
cout << "A(int a)" << endl;
cout << x << " " << y << endl;
};
A(const A&a)
{
x = a.x;
y = a.y;
cout << "A(A&a)" << endl;
}
private:
int x;
int y;
};
int main()
{
int a = 6;
A tmp = a;
return 0;
}
输出的结果:
当构造函数与需要类型转换的内置类型变量无关时。上面的例子就是,虽然传入了a但是没有使用a对类中的成员变量初始化,而是使用b的缺省值进行初始化,这时虽然将int类型转换为A类型但是是与a无关的。
进阶:
上面在演示程序逻辑时,a变量作为实参,去匹配构造函数的参数列表时,并没有说参数的数量,实际上a不只是能匹配一个参数的构造函数,还可以匹配多个参数的构造函数(其他第一个参数,其他的参数都需要有缺省值才行)。其实自定义的类型的隐示类型转换就是通过直接构造函数完成的,那能不能传入多个不同的类型转化为自定义类型?
class A
{
public:
//构造函数
A(int a,int b)
: x(a),
y(b)
{
cout << "A(int a)" << endl;
cout << x << " " << y << endl;
};
private:
int x;
int y;
};
int main()
{
int a = 6;
int b = 7;
A tmp = {a,b};
return 0;
}
程序运行的结果:
结论:
1.并不是只能一个类型进行隐示类型转换,多个类型也是可以隐示类型转换的,多个类型隐示类型转换的步骤和上面一样,只是这次向构造函数的传参不是一个。
2.构造函数不仅可以构造和初始化对象,对于单个参数或者除第一个参数无默认值其余均有默认值的构造函数,还具有类型转换的作用。
1.2.1 explicit关键字
上面讲了自定义类型的隐示类型转换是通过构造函数来完成隐示类型转换,当我们不允许隐示类型转换时,会在构造函数前面加上explicit这个关键字。
class A
{
public:
//构造函数
explicit A(int a,int b)
: x(a),
y(b)
{
cout << "A(int a)" << endl;
cout << x << " " << y << endl;
};
private:
int x;
int y;
};
int main()
{
int a = 6;
int b = 7;
A tmp = {a,b};
return 0;
}
这段程序是报错的,原因就是在构造函数前面加上了explicit关键字,还进行了隐示类型转换。
2. Static关键字
2.1 Static类成员
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量。
特性:
- 静态成员为所有的类型共有,放在静态区。
- 静态成员变量只创建一次,静态成员变量必须在类的外面定义,定义是不加static关键字。
- 静态成员变量可以使用:类名:静态成员或者对象.静态成员访问。
- 静态成员变量也是类型的成员,是受到public,private和protect的保护的。
代码演示
class A
{
public:
//构造函数
A(int a,int b)
: x(a),
y(b)
{
cout << "A(int a)" << endl;
cout << x << " " << y << endl;
};
private:
int x;
int y;
static int c;
};
int A:: c = 5;
int main()
{
int a = 6;
int b = 7;
A tmp = {a,b};
return 0;
}
2.2 Static修饰成员函数
用static修饰的成员函数,称之为静态成员函数
特性:
- 静态成员函数被static修饰,没有this指针,只能访问静态成员变量。
- 静态成员函数是不在成员函数存储的地方的。
代码演示:
class A
{
public:
//构造函数
A(int a, int b)
: x(a),
y(b)
{
};
static int get()
{
return c;
}
private:
int x;
int y;
//静态成员变量的声明
static int c;
};
//静态成员变量的定义
int A:: c = 5;
int main()
{
int a = 6;
int b = 7;
A tmp = {a,b};
cout << tmp.get() << endl;;
return 0;
}
输出的结果:
3. const修饰
const放在成员函数的参数括号的后面,这个const是用来修饰*this指针的,是为了不让通过this指针修改类中的成员变量。如:const *this
代码演示:
class A
{
public:
//构造函数
A(int a, int b)
: x(a),
y(b)
{
cout << "x=" << x <<" " << "y=" << y << endl;
};
//const修饰的成员函数
void get()const
{
//这个是不能修改类中的成员变量的
x = 4;
y = 5;
}
private:
int x;
int y;
static int c;
};
代码结果:
这个代码是报错的
4. 友元类和友元函数
4.1 友元函数
友元函数可以直接访问类的私有成员变量,它的定义是在类的外面的普通函数,不属于任何类,但是需要在类中声明,声明时需要加friend关键字。
特性:
- 友元函数可以访问私有和保护成员变量
- 友元函数没有this指针,所有不能const修饰
- 友元函数可以在类的任何位置进行声明,不受访问限定符的限制
- 一个函数可以是多个类的友元函数
- 友元函数和普通函数的调用是一样的
代码演示:
#include<iostream>
using namespace std;
class A
{
public:
//构造函数
A(int a, int b)
: x(a),
y(b)
{
cout << "x=" << x <<" " << "y=" << y << endl;
};
friend void get(int& n, int& m, const A& wa);
private:
int x;
int y;
static int c;
};
int A:: c = 5;
void get(int& n,int& m,const A& wa)
{
n = wa.x;
m = wa.y;
}
int main()
{
int a = 6;
int b = 7;
A tmp = {a,b};
int n, m;
get(n, m, tmp);
cout << "n=" << n << " "<<"m=" << m << endl;
return 0;
}
代码运行结果:
**结论:**当我们在类的外部定义的普通函数需要使用类中的成员变量时,就可以在该类中声明友元函数,这个该普通函数就可以访问类中的成员变量。
4.2 友元类
有一个类是另一个类的友元类,该类型中的成员函数都另一个类的友元函数,该类的成员函数都可以访问另一个类的非公有成员变量。
特性:
- 友元关系是单向的,比如:A是B的友元类,A是可以访问B中的成员变量,但是B不是A的友元,B是不可以访问A中的成员变量的。
- 友元关系是不能传递的,A是B的友元,B是C的友元,这并不意味这A是C的友元。
- 友元关系是不能继承的。
代码演示:
#include<iostream>
using namespace std;
class A
{
public:
//构造函数
A(int a, int b)
: x(a),
y(b)
{
cout << "x=" << x <<" " << "y=" << y << endl;
};
friend class B;
private:
int x;
int y;
static int c;
};
class B
{
public:
int get(const A& a)
{
return a.x;
}
};
int A:: c = 5;
int main()
{
int a = 6;
int b = 7;
A tmp = {a,b};
B tm;
int n = tm.get(tmp);
cout << n << endl;
return 0;
}
代码运行的结果:
5. 内部类
概念:一个类定义在另一个类的内部,就称该类是另一个类的内部类。
特性:
- 内部类和外部类是平等的,内部类虽然在外部类中定义,但是内部类是不属于外部类的,sizeof(外部类)是不会算上内部类的。
- 内部类是外部类的友元类,他们之间遵循友元关系的定义和用法,但是外部类不是内部类的友元。
- 内部类可以定义在外部类的任何位置。
- 内部类是可以直接访问外部类中的static成员变量的,不需要加上对象名/类名。
- 虽然说内部类和外部类是平等的,但是内部类需要定义是需要通过外部类的。
代码演示:
#include<iostream>
using namespace std;
class A
{
public:
//构造函数
A(int a, int b)
: x(a),
y(b)
{
cout << "x=" << x <<" " << "y=" << y << " " << "c=" << c << endl;
};
class B
{
public:
int get(A& a)
{
return c;
}
};
private:
int x;
int y;
static int c;
};
int A:: c = 5;
int main()
{
int a = 6;
int b = 7;
A tmp = {a,b};
A::B p;
cout << p.get(tmp) << endl;
return 0;
}
代码的运行结果:
6. 匿名对象
特性
- 匿名对象是不需要取名字的
- 匿名对象的生命周期只有它定义的这一行
代码演示:
#include<iostream>
using namespace std;
class A
{
public:
A(int a = 0)
:_a(a)
{
cout << "A(int a)" << endl;
}
~A()
{
cout << "~A()" << endl;
}
private:
int _a;
};
int main()
{
A();
return 0;
}
程序运行的结果:
7. 拷贝时编译器的一些优化
- 连续构造+拷贝构造直接优化为直接构造。
- 连续拷贝构造+拷贝构造直接优化为拷贝构造。
- 连续拷贝+赋值重载是没有办法进行优化的。