0.关注博主有更多知识
C++知识合集
目录
1.再谈构造函数
1.1初始化列表
1.2初始化列表的初始化顺序
1.3构造函数的隐式类型转换
1.4explicit关键字
2.static成员
2.1static成员变量
2.2static成员函数
3.友元
3.1友元函数
3.2友元类
4.内部类
5.匿名对象
6.编译器对拷贝对象的一些优化
7.练习题
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;
};
即使这样做我们依然能够达到我们所谓的初始化效果,但我们需要注意,有些变量是必须在定义时给定初始值的,比如const变量、引用变量以及自定义类型对象。
1.1初始化列表
我们将上面的Date类修改成正确的成员初始化版本:
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;
};
可以看到在构造函数的声明和函数体中间多了一个我们从未见过的东西,这个东西就叫做初始化列表。初始化列表的作用就是初始化成员变量,也就是说定义对象时调用构造函数,对象中的成员在构造函数的初始化列表当中初始化。
初始化列表的格式为:以一个冒号开始,紧接着是一个以逗号分隔的数据成员列表,每个成员变量后面跟一对小括号,小括号里面放的内容就是初始化的值。同时需要注意,每个成员变量在初始化列表当中只能出现一次,也就是只能初始化一次。
初始化列表是一直存在的,不管是哪一类的构造函数当中,即使我们不显式的写初始化列表,它依然存在。这也就意味着类的所有成员变量(除了静态成员)在定义时都要经过初始化列表。这里再次强调一点,类当中的成员都是声明,只有实例化对象的时候才发生定义。
那么对于普通成员变量来说,我们不在初始化列表当中显式初始化它们,它们就会被编译器用随机值来初始化:
#include <iostream>
using namespace std;
class Date
{
public:
Date(int year = 1,int month = 1,int day = 1)
{
cout << _year << ":" << _month << ":" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1;
return 0;
}
我们之前说过编译器默认生成的构造函数初始化自定义类型的对象时,会调用它的默认构造。那么如今再完善一下这个说法:无论是哪种类型的构造函数,自定义类型对象都会经过初始化列表,如果初始化列表当中没有显式初始化该对象,就会调用其默认构造,如果没有默认构造,那么就报错:
class A
{
public:
A(int a)// A类没有默认构造
:_a(a)
{}
private:
int _a;
};
class Test
{
public:
Test()
:_x(3)// 初始化列表没有显式初始化对象,调用默认构造,但是A类没有默认构造,报错
{}
private:
const int _x;
A _aa;// 自定义类型对象
};
int main()
{
Test t1;
return 0;
}
上面的内容看起来比较乱,在这里总结一下:
1.类实例化对象的时候,对象的成员被定义,被定义的地方在初始化列表
2.对象的每个成员都会经过初始化列表,无论初始化列表有没有显式的写出来或者初始化列表当中没有显式初始化该成员
3.如果显式的在初始化列表当中显式初始化成员了,那么就用显式写的初始化方法初始化成员
4.如果没有显式的在初始化列表当中初始化成员,就分为两种情况:
1)如果成员为内置类型,那么就用随机值初始化
2)如果为自定义类型,就会调用它的默认构造,如果没有默认构造,那么就报错
5.C++11给出了一种特殊情况,即成员的缺省值,既然作为缺省值这就验证了类当中的成员是声明而不是定义。那么在初始化列表当中,如果我们没有显式初始化成员,就会检查该成员有没有缺省值,如果有缺省值,就用缺省值初始化该成员;当然,不想使用缺省值时也可以显式的在初始化列表当中初始化:
#include <iostream>
#include <cstdlib>
using namespace std;
class Test
{
public:
Test()
:_y(30)
, _a((int*)malloc(100))// 只是演示一下可以这么完,因为这里相当于int* _a = (int*)malloc(100)
{
cout << "_x = " << _x << " _y = " << _y << endl;
}
private:
int _x = 3;
int _y = 5;
int* _a;
};
int main()
{
Test t1;
return 0;
}
当类中包含以下任意一个成员时,它必须在初始化列表进行初始化:
1.const成员变量:哪怕我们将const变量不作为成员变量,在外部定义时不给初值也是会报错的:
int main()
{
const int x;// 不给初值
return 0;
}
那么const变量正确定义之后是不能被赋值的:
int main()
{
const int x = 100;
x = 300;// 错误
return 0;
}
所以在类中,const成员变量必须在构造函数中的初始化列表显式初始化:
class Test
{
public:
Test()
:_x(3)
{}
private:
const int _x;
};
2.引用变量:我们已经证实过了引用在定义时不给初值是会报错的(如果忘了的朋友赶紧去我的主页点开C++的Chapter1),所以当引用变量作为类的成员时,必须在构造函数中的初始化列表显式初始化:
double val = 3.14;// 全局变量
class Test
{
public:
Test()
:_x(3)
, _rf(val)// 引用全局变量
{}
private:
const int _x;
double& _rf;
};
3.没有默认构造函数的自定义类型对象:如果不在初始化列表当中显式初始化,那么就会调用默认构造函数,但是如果对象没有默认构造函数,就会报错。所以没有没有默认构造函数的自定义类型对象必须在初始化列表当中显式初始化:
class A
{
public:
A(int a)
:_a(a)
{}
private:
int _a;
};
class Test
{
public:
Test(int x)
:_aa(x)// A类没有默认构造,必须显式初始化
{}
private:
A _aa;
};
int main()
{
Test t1(20);
return 0;
}
由此可见,初始化列表是相当重要的,也就是说,能在初始化列表当中初始化的成员,尽量在初始化列表当中初始化。
1.2初始化列表的初始化顺序
初始化列表当中出现的显式初始化,不是真实的初始化顺序,真实的初始化顺序与成员的声明顺序有关(我也不知道那帮大佬为什么要这样设计):
#include <iostream>
using namespace std;
class Date
{
public:
Date()
:_month(5), _day(_month), _year(2023)
{
cout << _year << ":" << _month << ":" << _day << endl;
}
private:
int _year;
int _day;
int _month;
};
int main()
{
Date d1;
return 0;
}
可以看到我用5去初始化_month,然后我的本意是初始化一个5月5日出来,但是打印结果确实上面那样。事实上,无论初始化列表的初始化是怎么写的,最终的初始化顺序都是按照成员声明的顺序来的。在这个例子中,_year是最先声明的,所以最先使用2023初始化_year;其实是_day,所以会先使用_month的值初始化_day,但是因为_month还没有被初始化,所以_month当前是一个随机值,所以_day也是一个随机值;最后声明的便是_month,所以最后使用5去初始化_month。所以最后的结果就是_year=2023、_month=5、_day=随机值。
1.3构造函数的隐式类型转换
在C++11之前,即C++98当中,支持单参数的构造函数的隐式类型转换。注意,这个单参数不是指构造函数只有一个参数(只有一个参数的话那就只有一个this指针了),单参数指的构造对象时只需要给构造函数传递一个参数就可以实例化出对象的构造函数。嗯...非常不好理解,我们看一段匪夷所思的代码:
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 d1(2023, 5, 3);
Date d2(2023);
// 隐式类型转换的构造
Date d3 = 2025;
const Date& rd = 2025;
return 0;
}
这段代码是不会产生任何编译错误的。我们可以看到d3使用一个常量整数2025来初始化,常引用rd引用了一个常量整数2025,既然我们说了这是一种隐式类型转换,那么它一定会产生临时变量,所以"Date d3 = 2025"可以被解释为int类型强制类型转换成Date类型,具体的工作就是生成一个临时对象,该临时对象用2025来初始化,最后该临时对象拷贝给正在创建的d3,所以d3对象是用一个已经存在的临时对象来拷贝构造初始化的:
那么对于常引用rd来说也是类似的:
可以看到隐式类型转换构造对象时,会发生一次构造和一次拷贝构造,我们以一段代码来验证:
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
:_year(year), _month(month), _day(day)
{
cout << "构造函数" << endl;
}
Date(const Date& d)
{
_year = d._year;
_month = d._month;
_day = d._day;
cout << "拷贝构造" << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d3 = 2025;
return 0;
}
但是最终的输出结果只有一个构造函数,是我们的理论错了吗?实际上这是编译器优化的结果,因为编译器认为构造函数的隐式类型转换是一次脱裤子放屁的过程,先构造出临时对象,再用临时对象拷贝构造给正在创建的对象,编译器一看,觉得麻烦,索性直接越过了拷贝构造这个过程,所以上面的输出结果只有构造函数被调用,所以构造函数的隐式类型转换被优化成了只发生构造,不发生拷贝构造。当然了,这个编译器一定是现代流行的编译器,十几年前的老古董可能不会做这种优化。
这种单参数的构造函数的隐式类型转换C++98就已经支持了,那么可想而知C++11一定对这个点进行了扩展,即支持了多参数的构造函数的隐式类型转换,我们看一段代码:
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 d1 = 2023,5,3;// 错误写法
//Date d2 = (2023, 5, 3);// 错误写法
Date d3 = { 2023, 5, 3 };// 正确写法
return 0;
}
可以看到,在C++11当中,可以将构造函数所需的参数用一对大括号给圈起来,然后发生类对象构造时的隐式类型转换。
那么这种隐式类型转换有什么作用呢?提前透露一下后面的内容,我们在后面将会介绍string类,其中string类有这么一个构造函数:
即string类的对象可以用一个指针构造,那么我们举个例子:
#include <iostream>
#include <string>// string类的头文件
using namespace std;
int main()
{
//const char* str = "hello";
//string s = str;
string s = "hello";// 与上面的写法等价
cout << s << endl;// string也重载了<<
return 0;
}
上面的代码不是关键,关键是假设有一名为func的函数,其参数是一个string类型,那么我们就可以这么玩了:
void func(string s)
{}
int main()
{
func("hello");// 只需要这么去传,并且只发生构造
// 不需要这样写了
//string s("hello");//一次构造
//func(s);//一次拷贝构造
return 0;
}
可以看到,构造函数的隐式类型转换还是有点作用的,再加上编译器的优化,使其不需要发生拷贝构造,所以这是一种高效、方便的代码编写手段。
1.4explicit关键字
在一些特定的设计场景当中,不希望发生构造函数的隐式类型转化,我们就可以在构造函数的声明之间加上explicit关键字:
class Date
{
public:
// 限制了隐式类型转换的发生
explicit 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 d1 = { 2023, 5, 3 };
Date d2 = 2024;
return 0;
}
2.static成员
首先实现一个小小的需求,即设计一个类,计算程序中创建了多少个该类的对象。首先我们要明白,对象的创建无非就两种方式,要么是通过构造函数,要么是通过拷贝构造,所以我们可以定义一个全局变量,来计算创建了多少个对象:
int N = 0;
class A
{
public:
A()
{
++N;
}
A(const A& a)
{
++N;
}
};
int main()
{
A a1;
A a2 = a1;
cout << N << endl;
return 0;
}
这种方法虽然能够实现我们的需求,但是是不好的。因为N作为全局变量,从操作系统的角度来看,该变量能够被该进程当中的所有线程共享,也就是说所有线程都可以对该变量进行修改,而且不需要什么成本。总之一句话,这种做法是不安全的。
2.1static成员变量
使用static修饰类的成员变量时,我们就称该成员为static成员变量或者静态成员变量。我们知道,static修饰的局部变量修改其生命周期,使其声明周期延长到了全局,并且保存在数据段(静态区)中。那么静态成员变量也不例外,它也存储在数据段中,也就是说,静态成员变量不存储在对象当中,静态成员变量是所有对象共享的,该静态成员变量在所有对象当中只有一份,即静态成员变量属于整个类。同时,因为静态成员变量不属于任何一个对象,所以它的定义不能被放在构造函数或者拷贝构造当中,它必须在类外定义。我们将上面的全局变量N作为A类的静态成员变量:
class A
{
public:
A()
{
++N;
}
A(const A& a)
{
++N;
}
public:// 注意这里是公有
static int N;
//static int N = 0;// 错误的写法,不能给缺省值,因为静态成员变量不在构造函数中定义
};
// 静态成员变量在类外定义
//int N = 0;//这种写法是单独定义一个全局变量
int A::N = 0;// 指明类域
int main()
{
A a1;
A a2 = a1;
cout << A::N << endl;// 访问时只需指明类域即可
cout << a1.N << endl;// 静态成员变量在对象当中是共享的,所以也可以访问
A* pa = nullptr;
cout << pa->N << endl;// 我们说过对象的作用只是为了指明类域
return 0;
}
注意,A类的所有成员的访问限定符都是公有的,可以直接通过类域访问。
2.2static成员函数
如果我们将上面A类中的静态成员变量N的访问限定符修改为private,那么就无法通过类域指明直接访问。我们仍然有一招可用,那就是给A类再定义一个成员函数,这个成员函数的作用就是返回静态成员变量的值:
class A
{
public:
A()
{
++N;
}
A(const A& a)
{
++N;
}
int GetN()
{
return N;
}
private:
static int N;
};
int A::N = 0;
int main()
{
A a1;
A a2 = a1;
// 不能直接访问私有
//cout << A::N << endl;
//cout << a1.N << endl;
cout << a1.GetN() << endl;
cout << a2.GetN() << endl;
A* pa = nullptr;
cout << pa->GetN() << endl;// GetN()内部没有发生this指针的解引用
//cout << A::GetN() << endl;// 错误
return 0;
}
可以看到,普通成员函数的访问必须通过对象,无法直接指明类域而进行访问。那么注意到上面代码中的一个注释,即"GetN()内部没有发生this指针的解引用",这是为什么?我们明明在GetN()函数当中使用了静态成员变量N。这里需要说明一下,静态成员变量它不属于任何一个对象,而是属于整个类,既然不属于任何一个对象那么this指针当然访问不到它,既然访问它时根本不需要this指针,因为静态成员变量在类域当中是所有成员函数都可见的。
那么既然访问静态成员变量不需要this指针的参与,那么可以将GetN()设计成静态成员函数,静态成员函数的特性之一就是没有this指针:
class A
{
public:
A()
{
++N;
}
A(const A& a)
{
++N;
}
static int GetN()// 静态成员函数
{
return N;
}
private:
static int N;
};
int A::N = 0;
int main()
{
A a1;
A a2 = a1;
cout << a1.GetN() << endl;
cout << a2.GetN() << endl;
A* pa = nullptr;
cout << pa->GetN() << endl;
cout << A::GetN() << endl;
return 0;
}
因为静态成员函数没有this指针,所以它可以直接指明类域访问,这是静态成员函数和普通成员函数的区别之一。
那么最后总结一下静态成员变量和静态成员函数的特性:
1.静态成员变量是所有对象所共享的,但是它不属于任何一个成员,存放在数据段中
2.静态成员变量的定义和初始化必须放在类外
3.静态成员可以通过[类名::静态成员]或者[对象.静态成员]或者[对象指针->静态成员]的形式访问
4.静态成员函数没有this指针,这就注定了静态成员函数无法访问任何非静态成员
5.静态成员也是类的成员,它们也受访问限定符的限制
3.友元
友元是一种突破类的封装的方式,在某些情况下能够提供遍历,但是友元会增加代码的耦合度、破坏封装,所以能不用尽量不用。友元关系分为友元函数和友元类。
3.1友元函数
在上篇博客当中已经介绍过友元函数了,就是流插入运算符和流提取运算符重载的日期类例子。友元函数的定义非常简单,只需要在类的任意位置声明函数,并在其声明之前加上friend关键字即可。
友元函数的作用就是可以直接访问类的私有成员,使用友元函数时必须清楚以下几个概念:
1.友元函数可以直接访问类的私有和保护成员,但是友元函数不是类的成员函数
2.友元函数不能定义为const成员函数,因为const不是修饰函数的,修饰的是this指针,而友元函数不是类的成员函数所有没有this指针
3.友元函数的声明可以放在类的任何地方,并且不受访问限定符的限制(声明为私有、保护、公有都没有区别)
4.一个函数可以是多个类的友元函数,也就是说友元函数可以访问多个类的私有、保护成员
5.调用友元函数与普通全局函数的调用没有区别
3.2友元类
友元类的使用方法和友元函数一样,只需要在类中声明类,并且在声明之前加上friend关键字即可:
class A
{
/*在类中声明B类是A类的友元类*/
friend class B;
public:
private:
int _x = 1;
int _y = 2;
};
class B
{
public:
private:
A _aa;
int _z;
};
那么理所应当,友元类可以访问类中的私有、保护成员:
class A
{
/*在类中声明B类是A类的友元类*/
friend class B;
public:
private:
int _x = 1;
int _y = 2;
};
class B
{
public:
/*
*_aa对象调用它的默认构造
*直接在A类之外使用A类的私有成员
*/
B()
{
_z = _aa._x;
}
private:
A _aa;
int _z;
};
同时需要注意友元关系的使用规则:
1.友元关系是单向的,不具有交换性。例如B是A的友元,那么B可以访问A的私有、保护成员,但是A不能访问B的私有、保护成员
2.友元关系不能传递,例如C是B的友元,B是A的友元,但是C依然不能访问A的私有、保护成员
3.友元关系不能被继承(后面再说)
4.内部类
在类中再定义一个新类,这个新类就是内部类:
class A
{
public:
/*在A类中定义一个新类*/
class B
{
public:
private:
int _z;
};
private:
int _x = 1;
int _y = 2;
};
以上面这段代码为例,我们可以计算一下A类的大小是多少:
int main()
{
cout << sizeof A << endl;
return 0;
}
可以看到打印的结果与我们的直觉相违背,因为A类当中有3个int类型的成员变量,最后的结果应该是12。实际上我们的想法是错误的,在类中定义一个新类,这两个新类之间没有任何关系,即内部类与外部类没有任何关系,它们是两个独立的类。那么内部类的作用是什么?内部类天生就是外部类的友元,内部类可以直接访问外部类的任何成员:
class A
{
public:
/*内部类可以访问外部类的任何成员
*在类当中不能定义当前类的对象,只能定义指针或引用
*例如A a是错误的,A *a,A &ra则正确*/
class B
{
public:
void func(const A& aa)
{
cout << aa._x << " " << aa._y << endl;
}
};
private:
int _x = 1;
int _y = 2;
};
int main()
{
/*实例化A类型的对象不会实例化出B类型的对象
*因为它们两个是独立的类*/
A a1;
A::B b;
b.func(a1);
}
既然内部类天生是外部类的友元,并且它们两个之间没有任何关系,那么它们与下面这样的写法没有区别:
class A
{
public:
friend class B;
private:
int _x = 1;
int _y = 2;
};
class B
{
public:
void func(const A& aa)
{
cout << aa._x << " " << aa._y << endl;
}
};
但是这并不代表内部类没有用处,相反,存在即合理。那么内部类的具体作用我这里先卖个关子,在本篇博客的末尾将会有一道习题,其中就囊括了内部类的正确打开方式。
5.匿名对象
匿名对象就是没有名字的对象,实际上我们接触过匿名对象,那就是传值返回当中生成的临时对象、构造函数隐式类型转换当中生成的临时对象。当然,我们还可以显式定义匿名对象:
class A
{
public:
A(int x = 1, int y = 1)
{}
void testA(int n)
{
cout << n << endl;
}
};
int main()
{
/*想要访问某个成员函数必须通过对象
*如果仅仅是为了访问某个成员函数而创建对象就有点浪费了
*不是浪费什么资源,而是写起来麻烦*/
A a1;
A a2(100);
A a3(100, 200);
a1.testA(10);
a2.testA(100);
a3.testA(1000);
/*匿名对象可以在一行当中完成对象的实例化和函数调用*/
A().testA(20);
A(200).testA(200);
A(200, 300).testA(2000);
return 0;
}
匿名对象的定义其实很简单,就是定义对象时别给对象起名就行。那么因为匿名对象没有名字,所以它的生命周期不跟随对象所在的作用域,而是当前行,即匿名对象的生命周期只有当前行:
class A
{
public:
~A()
{
cout << "~A()" << endl;
}
};
int main()
{
A();
return 0;
}
匿名对象主要是方便传参和传值返回:
A func(const A& a)
{
/*如果单纯为了返回一个对象,这样写就行了*/
return A();
//A ret;
//return ret;
}
int main()
{
/*如果只是为了传参,那么没必要这么写*/
//A a;
//func(a);
/*匿名对象即可(减少代码量[手动狗头])*/
func(A());
return 0;
}
当然,"减少代码量"不是主要的原因,更重要的是能够触发编译器的优化机制。
6.编译器对拷贝对象的一些优化
在这之前介绍过构造函数的隐式类型转换,其中本应发生一次构造和一次拷贝构造,最后被编译器优化成了只发生一次构造。那么像这样在一行代码当中发生连续的构造、拷贝行为(包括函数调用),编译器有可能会对它们做出优化。优化行为通常发生在构造函数的隐式类型转换、传值传参、传值返回当中。下面我们依次介绍(隐式类型转换介绍过了):
以下所有的测试都使用这个类:
class A
{
public:
/*发生构造、拷贝构造时打印一下*/
A()
{
cout << "A()" << endl;
}
A(const A& a)
{
cout << "A(const A& a)" << endl;
}
};
1.传值传参:首先来看第一种场景:
void func(A a)
{}
int main()
{
A a;
func(a);
return 0;
}
可以看到输出结果,证明编译器并没有帮我们做出优化,原因就在于不符合优化的条件(一行代码中发生连续的构造、拷贝构造行为)。那么们使用匿名对象,使条件符合:
void func(A a)
{}
int main()
{
func(A());
return 0;
}
可以看到,调用func()函数的实参为一个匿名对象,匿名对象实例化时需要调用拷贝构造,那么打印结果打印构造是合理的。那么调用func()函数的实参既然是一个对象,传值传参时,应当发生一次拷贝构造,但是打印结果并没有打印,可以证明,编译器对其进行了优化。也就是说,在这个例子当中,func()函数的形参a本来需要拷贝构造的,但是编译器觉得有点"脱裤子放屁",直接将调用func()函数的实参交给形参构造(A类的构造函数没有参数)。
2.传值返回:先来看第一种场景:
A func()
{
A a;
return a;
}
int main()
{
A ret = func();
return 0;
}
按道理来说,在func()函数内部定义一个对象发生一次构造,执行return语句时生成一个临时对象发生一次拷贝构造,最后外部接收func()返回值的时候又会发生一次拷贝构造。但是输出结果证明了一件事,编译器做出了优化。优化的原因还是因为"脱裤子放屁",因为编译器认为,将返回值拷贝给临时对象,临时对象又拷贝给外部接收返回值的对象,那这多麻烦啊,不如直接将返回值拷贝给外部接收返回值的对象:
因为func()函数内部并没有发生一行代码中连续构造、拷贝的行为,所以我们现在使用匿名对象做返回值,可以触发编译器的极致优化:
A func()
{
return A();
}
int main()
{
A ret = func();
return 0;
}
因为匿名对象的生命周期只有定义的那一行,正常的过程应该是构造、拷贝构造给临时对象,临时对象拷贝构造给外部接收返回值的对象,其中临时对象拷贝给外部对象发生的拷贝构造的优化是必然的,而又因为匿名对象只有一行的生命周期,所以编译器索性直接把它也干掉了:
7.练习题
JZ64 求1+2+3+...+n
这道题限制了我们不能使用常规手段来解题,那么就需要使用"奇技淫巧"了。我们试想,以前学过的知识哪里可以与这道题产生关联?那就是静态成员,我们可以定义一个类,类中有两个静态成员,其中一个便是加数,另一个便是最终求的和。我们利用实例化对象时会自定调用构造函数的特性来解决这道题:
/*定义一个类*/
class Result
{
public:
Result()
{
_sum += _add;
++_add;
}
static int getSum()
{
return _sum;
}
private:
static int _add;
static int _sum;
};
/*题目要求从1加到n*/
int Result::_add = 1;
int Result::_sum = 0;
class Solution {
public:
int Sum_Solution(int n) {
/*如何不用循环定义多个对象?数组*/
Result arr[n];
return Result::getSum();
}
};
另外,我们还可以将Result类作为Solution类的内部类,体会一下内部类的作用:
class Solution
{
/*Result作为Solution的私有内部类
*因为Result的目的就是为了帮助Solution解决问题
*Result类仅被Solution利用,所以作为私有*/
private:
class Result
{
public:
Result()
{
_sum += _add;
++_add;
}
static int getSum()
{
return _sum;
}
};
public:
int Sum_Solution(int n) {
Result arr[n];
return Result::getSum();
}
private:
static int _add;
static int _sum;
};
int Solution::_add = 1;
int Solution::_sum = 0;
HJ73 计算日期到天数转换
这道题不难想到肯定与日期类有关,我们可以直接将我们以前所写的日期类直接拷贝过去,然后定义对象,然后两个对象相减即可得出结果(例如2023 5 6构造的对象利用运算符重载减去2023 1 1构造的对象),但是我们不那么做,我们使用一种更加简单的方法。因为每一年的天数都是固定的,要么是365天要么是366天,那么我们完全可以手动计算日期的天数和:
#include <iostream>
using namespace std;
int main()
{
int year = 0,month = 0,day = 0;
cin >> year >> month >> day;
/*一月有31天,1月到2月一共有59天,1月到3月一共有90天......*/
int getMonthSum[13] = {0,31,59,90,120,151,181,212,243,273,304,334,365};
/*先求除当前月之前的所有月天数和,再加上day*/
int res = getMonthSum[month-1] + day;
/*判断是否为闰年,如果是闰年,天数+1
*需要注意一定+1的条件一定2月之后*/
if(month > 2 && ((year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0)))
{
++res;
}
cout << res << endl;
return 0;
}
KY111 日期差值
这道题是有点难度的(对于我来说),所以我直接暴力解法,将以前所写的日期直接复制过去:
/*这里复制了整个日期类*/
int main()
{
int d1 = 0,d2 = 0;
cin >> d1 >> d2;
/*将整数的年、月、日分别取出来*/
int d1_day = d1 % 100,d1_month = d1 % 2000 / 100,d1_year = d1 / 10000;
int d2_day = d2 % 100,d2_month = d2 % 2000 / 100,d2_year = d2 / 10000;
Date date1(d1_year,d1_month,d1_day);
Date date2(d2_year,d2_month,d2_day);
cout << (date1 - date2)+1 << endl;
return 0;
}
KY222 打印日期
#include <iostream>
using namespace std;
int main()
{
int year = 0,day = 0;
while(cin >> year >> day)
{
int getMonthDay[13] = {0,31,28,31,30,31,30,31,31,30,31,30,31};
if((year % 4 == 0 && year % 100 != 0 ) || (year % 400 == 0))
{/*如果是闰年,2月天数变一下*/
getMonthDay[2] = 29;
}
/*从一月开始,如果day大于当前月的天数
*day-=当前月的天数,当前月加1*/
int month = 1;
while(day > getMonthDay[month])
{
day -= getMonthDay[month++];
}
printf("%04d-%02d-%02d\n",year,month,day);
}
return 0;
}
KY258 日期累加
#include <iostream>
using namespace std;
int getMonthDay(int year, int month)
{
int day[13] = { 0, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 };
// 如果是2月又是闰年
if (month == 2 && ((year % 4 == 0 && year % 100 != 0) || (year % 400 == 0)))
{
return 29;
}
return day[month];
}
int main()
{
int m = 0;
cin >> m;
while(m--)
{
int year = 0,month = 0,day = 0,num = 0;
cin >> year >> month >> day >> num;
day += num;
while(day > getMonthDay(year,month))
{
day -= getMonthDay(year,month);
++month;
if(month == 13)
{
++year;
month = 1;
}
}
printf("%04d-%02d-%02d\n",year,month,day);
}
return 0;
}