1.初始化列表
先看一段代码
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
}
private:
int size;
};
class A
{
public:
private:
Stack s;
int a;
};
int main()
{
A tmp;
return 0;
}
这段代码没问题,A没有显示实现构造函数,那么就调用编译器默认的构造函数,内置类型不管,自定义类型调用它的默认构造函数,所以这个代码没问题
所谓默认构造函数,就是不用传参就可以使用的构造函数,具体有三种,第一个是,我们实现了一个没有参数的构造函数,第二是全缺省的构造函数,第三就是我们没有实现构造函数,由编译器默认实现的构造函数,也是没有参数的
所以如果我们实现了一个Stack的构造函数,有参数,而且不是全缺省。那么就会出问题
class Stack
{
public:
Stack(int a)
{
cout << "Stack()" << endl;
}
private:
int size;
};
class A
{
public:
private:
Stack s;
int a;
};
int main()
{
A tmp;
return 0;
}
所以这样的话,A的构造函数只能由我们显示实现
但是怎么实现呢
A()
{
s(2);
a = 0;
}
这样实现吗,这样实现显然是不行的,因为栈的构造函数的调用是在定义的时候,而在函数体内部就已经说明了这个栈已经定义了,但是栈又没有默认构造函数,所以肯定会出问题。所以我们必须找一个定义的地方
所以我们引入初始化列表,这个东西就是类的成员变量定义的地方
A()
:s(2)
,a(2)
{
;
}
显然,初始化列表是在函数体的前面的,函数名的后面
还可以这样写
A(int a)
:s(a)
,a(a)
{
;
}
所以初始化列表,冒号开始,逗号分开,括号对于内置类型就是直接赋值。对于自定义就是调用参数调用构造函数
还有一些成员变量,比如引用,还有const参与的,这些都必须在定义的时候赋值,而且不能改变了,所以只能在初始化列表初始化
class A
{
public:
A(int a)
:s(a)
,a1(a)
,a2(a)
,a3(a)
{
;
}
private:
Stack s;
int a1;
const int a2;
int& a3;
};
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
}
private:
int size;
};
class A
{
public:
A(int a)
:a2(a)
,a3(a)
{
cout << a1 << endl;
}
private:
Stack s;
int a1;
const int a2;
int& a3;
};
对于有默认构造的自定义类型成员变量,没有在初始化列表也没事,会自动走的,会自动走初始化列表,走的是默认构造的,对于内置类型没有在初始化列表,也会走初始化列表,只不过可能是随机值,也可能是0,这个还是看编译器
class A
{
public:
A(int a)
:a2((int*)malloc(40))
{
cout << a1 << endl;
}
private:
Stack s;
int a1;
int* a2;
};
甚至对于指针类型还可以这样使用
这里我们建议以后都要用初始化列表去初始化
来看一道题
class A {
public:
A(int a)
:_a1(a)
,_a2(_a1)
{}
void Print() {
cout<<_a1<<" "<<_a2<<endl;
}
private:
int _a2;
int _a1;
};
int main() {
A aa(1);
aa.Print();
}
//A. 输出1 1
//B.程序崩溃
//C.编译不通过
//D.输出1 随机值
答案是选D,为什么呢,按照常理来说应该是选A的,但是为什么呢
这里要注意一下了,初始化列表的顺序是和成员变量的顺序一样的,因为成员变量a2在前面,所以先执行_a2(_a1),所以选D
class Stack
{
public:
Stack(int a)
{
cout << "Stack()" << endl;
}
private:
int size;
};
class A
{
public:
A(int a)
:s(2)
{
cout << a1 << endl;
cout << a2 << endl;
cout << a3 << endl;
}
private:
Stack s;
int a1=2;
const int a2=2;
int& a3=a1;
};
int main()
{
A a(4);
return 0;
}
class Stack
{
public:
Stack(int a)
{
cout << "Stack()" << endl;
}
private:
int size;
};
class A
{
public:
A(int a)
:a2(a)
,a3(a)
,s(2)
{
cout << a1 << endl;
cout << a2 << endl;
cout << a3 << endl;
}
private:
Stack s;
int a1=2;
const int a2=2;
int& a3=a1;
};
int main()
{
A a(4);
return 0;
}
通过这两个代码我们可以看出,我们还可以与成员变量的默认值结合,注意,自定义类型是不可以设置默认值的
如果一个成员变量没有在初始化列表,会去走默认值,这个默认值就相当于在初始化列表了
如果一个成员在初始化列表,那么就会直接走初始化列表,不会走默认值了
这里对于const那种类型,同时有默认值和初始化列表是不会冲突的,因为有初始化列表,默认值就没用,没有初始化列表,默认值就相当于在初始化列表了
最后再走函数体
说白了,初始化列表就是成员变量定义的地方
还要注意每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2. explicit关键字
在讲这个之前,我们先讲一下类型转换
class Stack
{
public:
Stack(int a)
{
cout << "Stack()" << endl;
}
Stack(Stack& s)
{
cout << " Stack(Stack& s)" << endl;
}
private:
int size;
};
int main()
{
Stack s1(2);
Stack s2 = 2;
Stack s3 = 23.23;
return 0;
}
在这个代码中,第一个定义对象的方法还可以理解,但是第二个呢
第二个是这样的,先用2来构造一个对象x,2传给a来构造,然后再将x拷贝构造给s1,所以过程是先构造,在拷贝构造,但是呢,对于这个过程,编译器进行了优化,可以直接用2来构造s1,所以只打印了一个,但是底层的逻辑我们要知道
第三个就是将23.23这个小数传给int型的a,会发生类型转换,会截断小数点后面的内容,然后在构造,在拷贝构造
所以这样就很方便了,利用隐式类型转换,以后才可能知道
有一些隐式类型转换我们要小心一点,尤其是与const相关的引用和指针,最有可能发生权限的放大
int main()
{
int i = 0;
double& j = i;
return 0;
}
比如说这个代码就不行,这里还会发生隐式类型转换,前面用括号括起来,像这样(double)这叫强制类型转换,会先创建一个double变量x,用来接受i隐式类型转换而来的东西,然后在引用给j,但是呢,临时变量x具有常性,这样会发生权限的缩小,所以有错,应该这样
int main()
{
int i = 0;
const double& j = i;
return 0;
}
int main()
{
const int a = 0;
int* b = &a;
return 0;
}
这个因为a是const,所以a的地址默认为const*int,所以这样又会权限的放大,所以不行
所以一般建议给引用加上const,不修改的话,因为权限的缩小是不会出问题的
int main()
{
const int a = 0;
const int* b = &a;
return 0;
}
上面那个代码针对的是单个参数的自定义类型的隐式类型转化
拥有多参数的构造函数的类也可以隐式类型转化
class Stack
{
public:
Stack(int a=3,int b=4)
{
cout << "Stack(int a=3,int b=4)" << endl;
}
Stack(const Stack& s)
{
cout << "Stack(Stack& s)" << endl;
}
private:
int size;
};
int main()
{
Stack s1(2, 3);
Stack s2 = { 2,3 };
return 0;
}
这个嘛,就是多参数的隐式类型转换,把2和3分别传给a和b,然后后面操作就是一样的了
然后就是explicit关键字,把这个关键字放在构造函数的前面,就会禁止发生隐式类型转换
class Stack
{
public:
explicit Stack(int a = 3, int b = 4)
{
cout << "Stack(int a=3,int b=4)" << endl;
}
Stack(const Stack& s)
{
cout << "Stack(Stack& s)" << endl;
}
private:
int size;
};
int main()
{
Stack s1(2, 3);
Stack s2 = { 2,3 };
return 0;
}
3.static成员
static可以修饰成员变量,修饰的成员变量必须要在类的外面进行定义,就相当于函数的定义与声明一样的
class Stack
{
public:
Stack()
{
cout << "Stack()" << endl;
}
Stack(const Stack& s)
{
cout << "Stack(Stack& s)" << endl;
}
private:
int i;
static int count;
};
int Stack::count = 0;
int main()
{
cout << sizeof(Stack) << endl;
return 0;
}
静态成员是存放在静态区的,所以不占类的大小
class Stack
{
public:
int Get()
{
return _count;
}
Stack()
{
++_count;
cout << "Stack()" << endl;
}
Stack(const Stack& s)
{
cout << "Stack(Stack& s)" << endl;
}
private:
int _i;
static int _count;
};
int Stack::_count = 0;
int main()
{
Stack s1;
Stack s2;
cout << s1.Get() << endl;
cout << s2.Get() << endl;
return 0;
}
而且这个静态成员变量是所有对象共同拥有的,意思是所有对象的_count都是一样的
如果静态成员不是私有的,那么静态成员的访问可以有两种方式,第一种就是Stack::,第二种就是s1.
这样的话,我们就可以来计算计算机到底创建了多少个类
Stack()
{
++_count;
cout << "Stack()" << endl;
}
这样的话,得到count就可以了
~Stack()
{
--_count;
cout << "~Stack()" << endl;
}
如果再加上这个的话,就可以计算计算机中到底有多少个有效的没有被销毁的对象
然后就是注意,static修饰函数就是静态函数,静态函数是不能访问非静态成员变量的
还有就是静态函数可以访问非静态成员函数,非静态成员函数不能访问静态函数,可以访问静态变量
静态函数的访问也是有两种,因为静态函数只能访问静态变量,所以可以随时访问
这一切的原因都是因为静态函数没有隐藏的this指针
4.友元
友元函数我们前面已介绍过,这里粗略介绍一下
友元函数声明在类里面,需要在前面加上friend,实现在类的外面
友元函数可以访问类的私有的成员变量
友元函数的调用与普通函数的调用原理相同
友元函数不能用const修饰
好的我们接下来讲一下友元类
所谓友元类,就是一个类是另一个类的友元,是另一个类的朋友,那么这个类就可以访问另一个类的成员变量
class Stack1
{
friend class Stack2;
public:
Stack1()
{
cout << "Stack1()" << endl;
}
Stack1(const Stack1& s)
{
cout << "Stack1(Stack1& s)" << endl;
}
private:
int _i;
};
class Stack2
{
public:
Stack2()
{
Stack1 s1;
s1._i;
cout << "Stack2()" << endl;
}
Stack2(const Stack2& s)
{
cout << "Stack2(Stack2& s)" << endl;
}
private:
int _i;
};
这里Stack2是Stack1的友元,的朋友,那么在实现Stack2的某些函数时,定义Stack1的变量时,就可以访问Stack的成员变量了,而Stack1这个类中就不可以访问Stack2的成员变量,因为我把你当朋友,你可能不会把我当朋友
还有就是友元关系不能传递
如果C是B的友元, B是A的友元,则不能说明C时A的友元。
至于两个类互为友元这种情况,比较复杂,我就不介绍了
5.内部类
所谓内部类就是在一个类里面在定义一个类,这就叫做内部类
class Stack1
{
public:
Stack1()
{
cout << "Stack1()" << endl;
}
Stack1(const Stack1& s)
{
cout << "Stack1(Stack1& s)" << endl;
}
class Stack2
{
public:
Stack2()
{
}
private:
int _i;
};
private:
int _i;
};
int main()
{
cout << sizeof(Stack1) << endl;
return 0;
}
你看就算在类里面定义一个类,这个类的大小也不会增加,关键原因是比如在建立一个Stack1的对象的时候,并不会建立一个Stack2的对象,所以大小就那么大
class Stack1
{
public:
Stack1()
{
cout << "Stack1()" << endl;
}
Stack1(const Stack1& s)
{
cout << "Stack1(Stack1& s)" << endl;
}
private:
int _i;
};
class Stack2
{
public:
Stack2()
{
}
private:
int _i;
};
int main()
{
cout << sizeof(Stack1) << endl;
return 0;
}
所以说这两段代码并没有什么区别,在类里面定义一个类,与在类外面定义一个类,两个并没有什么区别,区别就是定义在类里面的那个类的类域受到了限制,要访问的话,要说明在哪个域
int main()
{
cout << sizeof(Stack1) << endl;
Stack1 s1;
Stack1::Stack2 s2;
return 0;
}
比如这样就可以创建两个类了,这两个类之间没什么关系
里面那个类叫做内部类,外面的叫做外部类
还有就是要注意内部类天生就是外部类的友元,而外部类不是内部类的友元,所以内部类可以访问外部类的成员变量
class Stack1
{
public:
Stack1()
{
cout << "Stack1()" << endl;
}
Stack1(const Stack1& s)
{
cout << "Stack1(Stack1& s)" << endl;
}
class Stack2
{
public:
Stack2()
{
Stack1 s1;
s1._i1;
}
private:
int _i2;
};
private:
int _i1;
};
还有一个点就是内部类可以访问外部类的静态成员变量,而且是不需要建立一个对象就可以访问,是可以直接访问的
class Stack1
{
friend class Stack2;
public:
Stack1()
{
cout << "Stack1()" << endl;
}
Stack1(const Stack1& s)
{
cout << "Stack1(Stack1& s)" << endl;
}
class Stack2
{
public:
Stack2()
{
cout << ss << endl;
}
private:
int _i2;
};
private:
int _i1;
static int ss;
};
int Stack1::ss = 0;
6.匿名对象
所谓匿名对象就是创建一个对象,这个对象是没有名字的
class Stack1
{
friend class Stack2;
public:
Stack1()
{
cout << "Stack1()" << endl;
}
~Stack1()
{
cout << "~Stack1()" << endl;
}
private:
int _i1;
};
int main()
{
Stack1 s1;
//Stack1 s1();//不能这样定义
Stack1();
cout << endl;
return 0;
}
由这个我们可以看出Stack1();这一行就是创建了一个没有名字的对象,然后这个对象的生命周期只有这一行,过了这一行就析构了
有什么用呢,当你只想访问一次的时候,这个就有用
比如
Solution().Sum_Solution(10);
创建的时候直接就去访问了
以后遇到了再说
7.拷贝对象时的一些编译器优化
有一个优化我们已经讲过了,就是上面的隐式类型转换时,构造加拷贝构造就会转换为直接构造
第二个
class Stack
{
public:
Stack()
{
_i = 0;
cout << "Stack()" << endl;
}
Stack(const Stack&s)
{
_i = s._i;
cout << "Stack()" << endl;
}
~Stack()
{
cout << "~Stack()" << endl;
}
private:
int _i;
};
Stack f()
{
Stack s;
return s;
}
int main()
{
Stack s1 = f();
return 0;
}
这里是构造,拷贝构造,再加上拷贝构造,一般的编译器会把后面的两个拷贝构造优化为一个拷贝构造,但我的VS2022优化的比较厉害,只有一个构造
这些优化知道就可以了,并不用太了解
8.练习题
对于这道题,我们直接先定义一个类的数组,这个数组有n个的话,就相当于走了构造函数n次,在每次都相加i,就可以了,为保证每次值和循环的一样,我们要设置静态成员变量,还有要访问静态变量,就要建立一个静态函数,因为建立非静态函数的话,还要多创建变量
#include <linux/limits.h>
class A
{
public:
A()
{
sum+=i;
++i;
}
static int Get()
{
return sum;
}
private:
static int sum;
static int i;
};
int A::sum=0;
int A::i=1;
class Solution {
public:
int Sum_Solution(int n) {
A arr[n];
return A::Get();
}
};
方法二
class Solution {
public:
class A
{
public:
A()
{
sum+=i;
i++;
}
};
int Sum_Solution(int n) {
A arr[n];
return sum;
}
private:
static int sum;
static int i;
};
int Solution::sum=0;
int Solution::i=1;
方法二就是定义内部类
然后就是内部类可以访问外部类的静态变量
#include <iostream>
using namespace std;
int main() {
int arr[]={0,31,59,90,120,151,181,212,243,273,304,334,365};
//先定义一个数组来存储到某个月的所有天数
int year=0;
int month=0;
int day=0;
cin>>year>>month>>day;
int sum=arr[month-1]+day;
if(month>2&&((year%4==0&&year%100!=0)||year%400==0))
{
++sum;
}
cout<<sum;
return 0;
}
#include <climits>
#include <iostream>
using namespace std;
class Date
{
public:
int GetDay(int year,int month)
{
int arr[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
if(month==2&&((year%4==0&&year%100!=0)||(year%400==0)))
{
return 29;
}
return arr[month];
}
Date(int year,int month,int day)
{
_year=year;
_month=month;
_day=day;
}
Date(Date&d)
{
_year=d._year;
_month=d._month;
_day=d._day;
}
bool operator==(Date&d)
{
return _year==d._year&&
_month==d._month&&
_day==d._day;
}
Date&operator++()
{
_day++;
if(_day>GetDay(_year, _month))
{
_month++;
_day=1;
if(_month>12)
{
_month=1;
_year++;
}
}
return (*this);
}
bool operator>(Date&d)
{
if(_year>d._year)
{
return true;
}
else
{
if(_year==d._year)
{
if(_month>d._month)
{
return true;
}
else
{
if(_month==d._month)
{
return _day>d._day;
}
}
}
}
return false;
}
void operator=(Date&d)
{
_year=d._year;
_month=d._month;
_day=d._day;
}
private:
int _year;
int _month;
int _day;
};
int main() {
int a, b;
while (cin >> a >> b) { // 注意 while 处理多个 case
int year1=a/10000;
int month1=(a%10000)/100;
int day1=a%100;
int year2=b/10000;
int month2=(b%10000)/100;
int day2=b%100;
//定义两个日期类
Date time1(year1,month1,day1);
Date time2(year2,month2,day2);
Date max=time1;
Date min=time2;
if(min>max)
{
max=time2;
min=time1;
}
int gap=0;
while (!(min == max))
{
++min;
++gap;
}
++gap;
cout<<gap;
}
return 0;
}
// 64 位输出请用 printf("%lld")
#include <iostream>
using namespace std;
int main() {
int a, b;
while (cin >> a >> b) { // 注意 while 处理多个 case
int year=a;
int month=0;
int day=0;
int arr[]={0,31,59,90,120,151,181,212,243,273,304,334,365};
if((year%4==0&&year%100!=0)||year%400==0)
{
for(int i=2;i<13;i++)
{
arr[i]++;
}
}
for(int i=1;i<13;i++)
{
if(b<arr[i])
{
month=i;
day=b-arr[i-1];
break;
}
}
printf("%d-%02d-%02d",year,month,day);
}
}
// 64 位输出请用 printf("%lld")
#include <iostream>
using namespace std;
int GetDay(int year,int month)
{
int arr[]={0,31,28,31,30,31,30,31,31,30,31,30,31};
if(month==2&&((year%4==0&&year%100!=0)||(year%400==0)))
{
return 29;
}
return arr[month];
}
int main() {
int year,month,day,count;
int n;
cin>>n;
while(n--)
{
cin>>year>>month>>day>>count;
while(count--)
{
day++;
if(day>GetDay(year, month))
{
month++;
day=1;
if(month>12)
{
month=1;
year++;
}
}
}
printf("%d-%02d-%02d\n",year,month,day);
}
}
总结
类和对象就到此结束啦!!!!