目录
2.newcoder:求1+2+3+...+n_牛客题霸_牛客网
一、再谈构造函数
1.构造函数体赋值
在创建对象的时候,编译器通过调用构造函数,给对象中 各个成员变量一个合适的初始值。代码如下:
class Date
{
public:
Date(int year,int month,int day)
{
_year = year;
_month = month;
_day = day;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1; //是一种定义 对对象整体的定义 而不是对成员的初始化
...
const int i ; // 这种是错误的 因为const只初始化一次,必须在定义的时候同时初始化
}
虽然上述构造函数调用之后,对象中已经有了一个初始值,但是不能称其为对象中成员变量的初始化,构造函数体中的语句只能称其为赋初值,而不能称为初始化。因为初始化只能初始化依次,而构造函数体内可以多次赋值。
还有如下情况:
int i = 1; double d = i; //这里存在一个隐士的类型转换,产生了一个double的临时变量 //在c++中,对于类而言 如果要类型转换 A aa = 1; // 如果有隐式类型转换,则将int改为A 类型 产生一个临时变量 再以拷贝构造的方式给aa // 但是编译器优化过 不用产生临时变量 直接进行类型转换 // 如果类中有一个const成员变量 const int i ; //❌ const int i = 0; //√ const 变量只能初始化一次,所以必须要在定义的时候初始化 C++中,在进入构造函数体内时候,引用变量和const变量都已经用不确定的值初始化好了,构造函数内能做的的只有赋值,而const类型和引用类型不可以赋值,所以需要在初始化列表中初始化 // A& ref = 10 ; × // const A& ref = 10; √ 这里产生了临时对象 引用的时候不能优化 产生的临时变量具有常性
2.初始化列表(初始化成员)
初始化列表:以一个冒号开始,接着以逗号分隔的数据成员列表,每个成员变量后面跟一个放在括号中的初始值或表达式。示例如下:
class Date
{
public:
Date(int year, int month, int day)
:year(year)
,_month(month)
,_day(day)
{}
private:
int _year;
int _month;
int _day;
};
注意:
1.每个成员变量在初始化列表中只能出现一次(初始化只能初始化一次)
2.类中包含以下成员,必须在初始列表位置进行初始化:
- 引用成员变量
- const成员变量
- 自定义类成员(且该类没有默认构造函数)
3.尽量使用初始化列表初始化,不管是否使用初始化列表,对于自定义类型成员变量,一定会先使用初始化列表初始化
class Time { public: Time(int hour = 0) :_hour(hour) { cout<<"Time()<<endl; } private: int _hour; }; class Date { public: Date(int day) { } private: int _day; Time _t; }; int main() { Date d(1); }
4.成员变量在类中声明次序就是其在初始化列表中的初始化顺序,与在初始化列表中的先后次序无关
//三种必须用初始化列表初始化的情况
class A
{
public:
A(int a)
:_a(a)
{
}
private:
int _a;
};
class B
{
public:
B(int a, int ref)
:_aobj(a)
,_ref(ref)
,n(10)
{
}
private:
A _aobj; //没有默认构造函数
int & ref;
const int _n;
};
3.explicit关键字
构造函数不仅可以构造与初始化对象,对于单个参数或者除第一个参数无默认值,其余均有默认值的构造函数,具有类型转换作用。
用explicit修饰构造函数,会禁止构造函数的隐式转换。
4.匿名对象
匿名对象的特点就是声明周期只有一行,可以在函数返回的时候使用,或者只需要一次性调用的时候可以使用匿名对象,不用专门创建一个对象去调用成员函数
class A
{
public:
A(int a = 0)
:_a(a)
{
cout<<"A(int a)"<<endl;
}
~A()
{
cout<<"~A()"endl;
}
private:
int _a;
};
class Solution{
public:
int Sum_Solution(int n)
{
//...
return n;
}
};
int main()
{
A aa1;
// A aa1(); 不能这样定义对象,因为编译器无法识别这是一个函数声明还是对象定义
//但是可以定义匿名对象 匿名对象的特点就是不用取名字
// 但是它的生命周期只有一行,接下来就会自动调用析构函数
A();
}
class Sum
{
public:
Sum()
{
_sum += _i;
++_i;
}
static int GetSum()
{
return _sum;
}
private:
static int _i;
static int _sum;
};
int Sum::_i = 1;
int Sum::_sum = 0;
class Solution {
public:
int Sum_solution(int n)
{
Sum* ptr = new Sum[n];
return Sum::GetSum();
}
~Solution()
{
cout << "已经析构" << endl;
}
};
#include<iostream>
using namespace std;
int main()
{
//专门创建了一个对象
Solution s;
cout << s.Sum_solution(10) << endl;
//Solution s1(); // 不能这样定义 因为分不清楚是函数的定义还是声明
Solution(); //匿名对象 没有名字
//声明周期只有一行
//只需要一次性调用 就可以用匿名对象 不需要专门创建一个对象去调用成员函数
cout << Solution.Sum_Solution(10) << endl;
return 0;
}
A fun(int n)
{
int n;
cin>>n;
int ret = Solution().Sum_Solution(n);
//构建一个名字为retA 的对象返回
A retA(ret);
return retA;
//直接使用一个匿名对象返回
return A(ret);
}
二、Static成员
1.概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称为静态成员变量,用static修饰的成员函数,称为静态成员函数。静态成员变量一定要在类外初始化。
//面试题:实现一个类,计算程序中创建了多少个对象
class A
{
public:
A()
{
++count;
}
A(const A& t)
{
++count;
}
~A()
{
--count;
}
static int GetCount()
{
return count;
}
private:
static int count;
};
int A::count = 0;
void TestA()
{
cout<<A::GetCoun()<<endl;
A a1,a2;
cout<<A::GetCount()<<endl;
}
2.newcoder:求1+2+3+...+n_牛客题霸_牛客网
有一道题计算1+2+...N,这里就可以使用static 定义两个成员,i,sum,i表示加到n,sum表示总和,static一个成员函数,获取sum的值。这里在main中可以定义一个类数组,即调用n次构造函数,每次构造的时候都++sum,具体代码如下:
#include<climits> class Sum { public: Sum() { _sum += _i; ++_i; } static int GetSum() { return _sum; } private: static int _i; static int _sum; }; int Sum::_i = 1; int Sum::_sum = 0; class Solution { public: int Sum_Solution(int n) { Sum a[n]; return Sum::GetSum(); } };
- 静态成员为所有类对象共享,不属于某个具体的对象,存放在静态区
- 静态变量必须在类外定义,定义的时候不添加static关键字,在类里只是声明
- 类静态成员可用类名::静态成员或者对象.静态成员访问
- 静态成员函数没有隐藏的this指针,不能访问任何非静态成员
- 静态成员也是类的成员,受public,protected,private访问限定符的限制
三、友元
友元提供了一种突破封装的方式,有时候提供了遍历可以访问私有成员。但是友元会增加耦合度,破坏了封装,所以友元不宜多用。友元分为友元函数和友元类
1.友元函数
友元函数可以直接访问类的私有成员,它是定义在类外的普通函数,不属于任何类,但是需要在类中加friend关键字声明。
比如:尝试去重载operator<<,因为cout的输出流对象和隐含的this指针抢占第一个参数的位置,必须cout在左操作数才能正常使用,所以需要用operator<<重载为全局函数,但是全局函数无法访问类中的私有成员,此时需要friend来解决。
class Date { friend ostream & operaot<<(ostream& _cout, const Date &d); public: Date(int year,int month,int day) :_year(year) ,_month(month) ,_day(day) {} private: int _year; int _month; int _day; }; ostream& operator>>(ostream& _cout, Date& _d) { _cout<<d._year<<d._month<<_d._day<<endl; return _cout; }
2.友元类
友元类的所有成员函数都可以是另外一个类的友元函数,都可以访问一个类中的非公有成员。
class Time { friend class Date; // 声明的日期类是时间类的友元类,则在日期类中可以直接访问time类的私有成员 public: /// private: int _hour; int _min; int _s; } class Date { public: Date() { /// } //日期类为时间类的友元类,在日期类里可以访问时间类的私有成员,但是时间类不能访问日期类的私有成员 void TestTime(int hour,int min,int second) { _t._hout = hour; 、、、 } private: }
- 友元是单向的,不具有交换性
- 友元关系不能传递
- 友元关系不能继承
四、内部类
1.概念
如果一个类定义在另外一个类的内部,这个就称为内部类,内部类是一个独立的类,它不属于外部类,更不能通过外部类的对象访问内部类的成员,外部类对内部类没有优越的访问权限。
内部类就是外部类的友元类,内部类可以通过外部类的对象参数访问外部类的所有成员,但是外部类不是内部类的友元。
1. 内部类可以定义在外部类的public、protected、private都是可以的。2. 注意内部类可以直接访问外部类中的static成员,不需要外部类的对象/类名。3. sizeof(外部类)=外部类,和内部类没有任何关系。
六、拷贝对象时一些编译器优化
可以参考一本书: 深度探索c++模型
在传参和传返回值的时候,一般编译器会做一些优化,减少对象的拷贝。
总结:
对象返回问题:
1.接收返回对象尽量拷贝构造接收,不要赋值接收
// A aa1 = func() 拷贝构造接收
// func()
A aa2 = fun(); // 赋值接收
2.函数中返回对象时,尽量返回匿名对象
A func4()
{
return A() // 返回一个匿名对象
// 构造 + 拷贝构造 优化为直接构造// 所以可以直接返回匿名对象可以加速编译器的优化
}传参问题:
1.尽量使用 const & 引用传参
class A
{
};
// 传值传参
void func1(A aa)
{
}
//加const 可以接受普通对象也可以接受const对象
void func2(const A& aa)
{
//引用传参
}
//传值返回
A fun3()
{
A aa; // 构造
return aa; //拷贝构造 不会用aa作为返回值 因为aa出了作用域已经销毁 所以使用拷贝构造给一个变量
}
A func4()
{
return A() // 返回一个匿名对象 所以可以直接返回匿名对象可以加速编译器的优化
// 构造 + 拷贝构造 优化为直接构造
}
int main()
{
//传值传参
A aa1 = 1; // 构造 + 拷贝构造 优化为直接构造 1为int 构造一个A类型的对象 拷贝构造给aa1
func1(aa1); // 传值传参 不能直接优化 构造aa1 +拷贝构造给形参
// 有一个析构 aa形参的析构
func1(2); // 2构造一个A+拷贝构造 优化为直接构造
func1(A(3)); // 构造 + 拷贝构造 优化为直接构造
func2(aa1); //引用传参 不用构造和拷贝构造
func2(2); // 匿名对象 构造一个匿名对象 没有优化 结束直接析构
func2(A(3)); //构造 引用不用使用拷贝构造
fun3(); //不会优化
A aa1 = fun3(); // 构造aa 拷贝构造aa给一个变量 再拷贝构造给aa1 编译器直接优化为一个构造+一个拷贝构造
func4(); // 构造 + 拷贝构造 -- 优化为构造
A aa3 = func4(); // 构造 + 拷贝构造 + 拷贝构造 --- 优化为构造
}
七、再次理解类和对象
现实生活中,实体计算机只认识二进制格式的数据,如果想让计算机认识现实生活的实体,用户必须通过某种面向对象的语言,对实体进行描述,通过编写程序,创建对象后计算机才可以认识。
比如想让计算机认识洗衣机,就需要:
总结
本章对构造函数,static成员,友元,内部类,匿名对象,编译器的优化拷贝对象进行简单总结,技术有限,如有错误请指正。