目录
前言
今天,我们学完之后才算是真正的C++入门,终于入门了,欢迎来到最终篇
类与对象(下)
1.再续构造函数(初始化列表)
初始化列表是对创建变量的一个初始化定义
例外初始化列表也可以使用缺省值,但是如果初始化列表已经进行了初始化就不会再使用缺省值
任何类里面的对象在初始化的时候都需要走一遍初始化列表
初始化形式是这样的
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;
};
这是初始化列表的逻辑,大家可以参考一下。
另外,初始化的顺序是按照声明的顺序进行初始化,我们在创建初始化列表的时候也建议按照声明的顺序来创建。
关于初始化列表的总结:
(1)⽆论是否显⽰写初始化列表,每个构造函数都有初始化列表;
(2)⽆论是否在初始化列表显⽰初始化成员变量,每个成员变量都要⾛初始化列表初始化;
2.类型转换
什么是类型转换呢?
我举下面这个例子:
class A
{
public:
A(int a1)
:a0(a1)
{
}
private:
int a0
};
int main
{
//将1给临时对象,;临时对象拷贝构造给a
A a = 1;
return 0;
}
在这里我们发现如果运行这个代码并不会报错,对于将整型值给给我们的类类型又是什么意思呢
我们将这个整形给临时变量之后临时变量将它拷贝给_a,这叫做类型转换。
我们如果去掉构造函数就会报错,如果我们加上拷贝构造也不会显示出来。
因为编译器会将这个过程优化掉。我们可以通过Linux来看到这个过程。
隐式类型转换的特点:
(1)我们在构造函数前面加上explicit后,就不在支持隐式类型转换
(2)我们要想要相关内置类型允许隐式类型转换需要在构造函数的参数部分有相应的内置类型。
如果我们想要类类型之间允许隐式类型转换,那么同理。
3.友元函数
我们在第一次使用C++时,肯定对它的输出存有疑问,今天我们再来看,cin与cout实际上是std标准库里实现的多个运算符重载,这也就是为什么它能够自动识别多种内置类型并能够进行输入和输出。
另外,如果让我们输入或者输出一个自定义类型,比如说日期类,标准库里的肯定不知道我们要如何输出,那就需要我们自己对这个输入输出进行运算符重载。
ostream& operator<< (ostream& out)
{
out << _year << " " << _month << " " << _day << endl;
}
如果我们这样像往在类里面去实现就会发现当我们使用的时候不再是cout << d,而是d << cout,与理想的方向不对了,因为我们想要的是流向控制台,像内置类型一样输出,在根本上,我们能看出是因为this指针的问题。
那我们换一种方式去写,不在类里面定义,放在全局中,就不会受到this指针的影响,同时问题又一次出现了,这个类里面私有的成员变量我们没办法访问,怎么办?
重点来了,我们在类里面写一个声明,并在前面加上一个friend ,这就是友元声明,当然如果有多个cpp文件,定义全局函数编译器会报错,所以我们可以定义与声明分开,也可以在定以前加个inline,加上inline之后就会在当前文件展开,也不会出现在符号表中。
//这里只实现inline函数部分,Date是类类型
//这里能访问私有成员的原因是前面在类中的声明里加了friend
inline ostream operator<<(ostream& out ,const Date* this)
{
out << _year << " " << _month << " " << _year;
}
友元函数的特点:
(1)外部友元函数可以访问内部类里面的私有和保护成员。
(2)友元函数可以在类定义的任何地⽅声明,不受类访问限定符限制。
(3) ⼀个函数可以是多个类的友元函数。
(4)友元类中的成员函数都可以是另⼀个类的友元函数,都可以访问另⼀个类中的私有和保护成员。
(5)友元类的关系是单向的,不具有交换性,⽐如A类是B类的友元,但是B类不是A类的友元。
友元类关系不能传递,如果A是B的友元, B是C的友元,但是A不是C的友元。
(6)友元有时提供了便利。但是友元会增加耦合度,破坏了封装,所以友元不宜多⽤。
4.static成员
static可以用在成员变量前面,也可以放在成员函数前面
放在成员变量前面,说明把它放在了静态区,我们在讲实例化对象的时候将类比作图纸,创建不同的类的对象比作一栋栋楼,那静态成员变量就是他们的公共区域,不占用他们开辟的栈帧,因此在计算某个对象的大小的时候,不用将它计算进去。
而且由于静态成员变量不属于任何一个单独创建的对象,因此静态成员变量的初始化要在全局域中
class A{
public:
private:
static int _a;
}
int A::_a = 0;
当然我们可以有
1.static成员变量是公有的,
两种方式去访问它,第一种创建成员变量访问它
第二种就是,直接使用域访问操作符,通过类去访问
int main()
{
A a1;
cout << a1.a;
cout << A::a;
return 0;
}
2.static是私有的
我们可以通过成员函数去访问
class A{
public:
int GetAccont()
{
return _a;
}
private:
static int _a;
}
int A::_a = 0;
int main()
{
cout << A::GetAccont();
return 0;
}
关于静态的成员函数,静态的成员和函数是没有this指针的,因此我们使用静态的成员函数不能访问非静态的成员变量
而非静态的成员变量可以去访问静态的以及非静态的成员变量。
(2)我们可以通过一道例题来加深对static 的理解
博主的解题思路如下:这里编译器是支持变长数组的,调用构造函数n次,又由于static成员变量存储在静态区中,就可以实现n次叠加,最后就涉及到我们刚刚学习到的知识点,如何访问私有的静态成员变量,通过静态成员函数来实现。
5.匿名对象
匿名对象简单来说就是不需要起名字,它的作用就是一定程度方便我们去访问类里面的成员函数
举个例子
class solution{
public:
void solutinway()
{
}
};
int main()
{
solution(2);
solution().solutionway();
return 0;
}
如果我们再去创建变量,再去调用这个函数,就要麻烦一些了,匿名函数可以简化这过程。
6.内部类
内部类就是在类里面又定义的一个类。
(1)外部类定义的对象不包含内部类,内部类仅仅收到外部类类域限制和访问限定符限制。
(2)内部类,是外部类的一个友元函数。
也就是说外部类的私有的成员内部类可以默认访问。’
当我们想要这个类不被轻易调用,我们可以将它作为内部类去使用,也是封装的一种体现
(3)学完内部类我们可以对这个题实现进一步的优化
我们这样去实现,不仅减少了代码量,还变得更加好理解。
思路是相同的,只是内部类可以直接访问外部类的默认静态成员变量。
7.编译器的优化
我们看下面几个例子
int main()
{
// 传值传参
// 构造+拷⻉构造
A aa1;
f1(aa1);
cout << endl;
// 隐式类型,连续构造+拷⻉构造->优化为直接构造
f1(1);
// ⼀个表达式中,连续构造+拷⻉构造->优化为⼀个构造
f1(A(2));
// 传值返回
// 不优化的情况下传值返回,编译器会⽣成⼀个拷⻉返回对象的临时对象作为函数调⽤表达
式的返回值
// ⽆优化
// ⼀些编译器会优化得更厉害,将构造的局部对象和拷⻉构造的临时对象优化为直接构造
f2();
cout << endl;
// 返回时⼀个表达式中,连续拷⻉构造+拷⻉构造->优化⼀个拷⻉构造
// ⼀些编译器会优化得更厉害,进⾏跨⾏合并优化,将构造的局部对象aa和拷⻉的临时对象
和接收返回值对象aa2优化为⼀个直接构造。
return 0;
}
如果是按我们所学,一步一步来的话,这个程序应该先经过产生临时对象,然后进行拷贝构造,再析构
可我们观察相对较新的编译器一般都对这个过程进行了优化,优化为直接进行构造,这样可以提高编译器的效率。
所以,很多情况下我们看不到许多过程,看不到调用拷贝构造的过程,不是没有而是被编译器优化了,越新的编译器优化的通常越厉害。
总结
今天,我们进一步学习了C++的基础语法,将类与对象这部分进行了学习,成功入门C++!