目录
②返回值优化之一:定义+=和 +的时候 可以其中一个进行返回值优化
③保存原始赋值:可以用临时对象,保存当前值 、通过赋值函数 恢复
①若需要在外部修改,类的私有数据成员,可以定义一个公有引用函数,
但是这样会让这个函数不仅有取得这个值的功能,也有修改值的功能
所以可以分别定义
class A{
int Getdata() {return data;}
void Refdata(int num) {data-=num;}
void AddData(int num) {data+=num;}
/*int & Getdata() {return data;} */
private:
int data;
};
②返回值优化之一:定义+=和 +的时候 可以其中一个进行返回值优化
MyString operator +(){
return MyString(a)+=b;
}
相当于创建就返回,直接返回当前对象了。不会再给返回值也构造一次拷贝构造函数。
若
MyString temp(a);
temp+=b;
return temp; //这样的话,若没有进行返回值优化会使得返回的时候也会调用一次拷贝构造函数。
返回值优化就是直接将对象返回。
③保存原始赋值:可以用临时对象,保存当前值 、通过赋值函数 恢复
④赋值函数返回值
class My{
public:
My& operator =(const My & rhs){
if(this!=&rhs){
}
}
};
连续赋值:
(a=b)=c;
所以要求返回值为非const型
rhs为const型引用,&rhs也为const型地址,不可赋值为 非const指针,但是比较地址是否相同的时候不会修改值,所以可以比较
为什么要用地址比较是否相同?
因为如果用对象的话 比如*this!=rhs,相当于 对象认识 操作符!=,那么要重新重载一个!=函数。
⑤类的静态数据成员初始化
非静态数据成员都只能在类内部初始化;
静态数据成员除了 static int const a可以在内部直接初始化外,其他的都要在外部进行初始化。(没有在外部初始化,则属于声明,未定义,不分配空间)
class A {
public:
A() {a=1;} //error : a未定义。
static int a;//声明 未定义
};
注意:
定义只能在类外定义,若没有初始化,则只有声明
只有声明,不使用是可以的,但是要使用就会报错。
所以静态数据成员的初始化 一定比 实例化早。当然也可以只定义,只要初始化就必须是在实例化前。实例化的时间和顺序,与静态数据成员的初始化没有关系。静态数据成员在类诞生的时候就产生了。
注意规避:我们所说的未定义只声明但不会报错是函数和数据的共性,声明是可以的 ,并且未定义只声明不能使用。
⑥头文件顺序
"" 放在<>的后面,因为<>一般不出错,如果""写错了就会报""的错误,
但是如果""放前面,可能""与后面的<>有些冲突的地方,那么会报<>的错误。所以
#include<iostream>
#include"a.h"
⑦拷贝构造函数和赋值函数
对于B和C类而言,若B和C类没有自定义拷贝构造函数和赋值函数,那么在进行拷贝构造和赋值的时候,会同时调用A类的拷贝构造和赋值函数(此函数形式只有一种,有自定义的就会调用A类自定义的),如果没有则会报错。
如果B和C类有自定义的拷贝构造函数,由于A类一定要初始化,若没有对A做要求则A类会自动调用其默认构造函数(没有默认构造函数则会报错,由于你没指定要调用拷贝构造 不会调用拷贝构造!把它当做一个普通构造函数就行),若对A的调用做要求可以自定义规定调用。
如果B和C类有自定义的赋值函数,若不进行调用A类的,则不会调用,如果需要调用A类的赋值函数,需要自己调用。
class A{//A类拷贝构造和赋值已经定义
public:
A(int num) {}
A(const A & t) {cout<<1;}
A & operator =(const A & t) {cout<<11;}
};
class B{//有A类对象
public:
B():a(1) {}
B& operator =(const B & t) {}
private:
A a;
};
class C:public A{ //A类继承
public:
C():A(1) {}
C& operator =(const A& t) {}
};
#include<iostream>
using namespace std;
class Data {
public:
Data(int x=0) {
Data::x=x; cout<<"Data( )"<<endl; }
Data(const Data&) {cout<<"Data(const Data&)"<<endl; }
~Data( ){ cout<<"~Data( )"<<endl; }
private: int x;
};
class Base {
public:
Base( ) { cout<<"Base( )"<<endl; }
Base(const Base&) {cout<<"Base(const Base&)"<<endl; }
virtual ~Base( ) { cout<<"~Base()"<<endl;}
};
class Child:public Base{
public:
Child( ) { cout<<"Child( )"<<endl; }
Child(const Child & t) {cout<<"Child( )"<<endl; }
virtual ~Child( ) {cout<<"~Child( )"<<endl; }
private:
Data d2;
};
int main( )
{
Child c1;
Child c2(c1);
return 0;
}
输出:
Base( ) //调用Child::Child() 构造c1,一定会先构造基类
Data( ) //构造完基类,再经过初始化列表初始化,先构造子对象
Child( ) //初始化列表完毕,输出Child()
Base( ) //拷贝构造,由于有自定义的拷贝构造函数,先调用基类的默认构造函数
Data( ) //由于有自定义的拷贝构造函数,调用子对象的默认构造函数
Child( ) //初始化列表完毕,输出Child()
~Child( ) //先析构c2,先调用对象的析构函数。
~Data( ) //对c2进行销毁,调用子对象的析构函数
~Base() //最后对基类销毁,调用基类的析构函数
~Child( ) //析构c1
~Data( )
~Base()
若去掉Child(const Child & t); 拷贝构造c2时,由于没有Child自定义拷贝构造函数,会调用子对象和基类的拷贝构造函数:
Base(const Base&)
Data(const Data&)
⑧名字空间问题
//main.cpp
namespace{ int num = 10; } //匿名名字空间,文件级,在全局名字空间内
namespace Foo{ int num = 30; }
void func1(){ cout << num << endl; }
void func2();
int main() {
func1();
func2();
cout<<num<<endl;//当前名字空间 找不到,在全局名字空间里找
using Foo::num;
cout<<num<<endl;//在当前名字空间内找得到。 ::num表示全局名字空间,num表示当前名字空间。
}
//func.cpp
int num = 20;
void func2(){ cout << num << endl; }
namespace A{
int a;
namespace B{
int b=2;
}
}
using namespace std;
using namespace A::B;//将名字空间A下的 名字空间B 汇入到当前名字空间
using A::a; //将名字空间A下的 名字a汇入到当前名字空间
A::B::b==2; //通过作用域限定符 直达内部
⑨类间水平关系
分为依赖和关联关系。
1.关联关系是强调非偶然性“知道”,“知道”不一定要时刻含有,也可以是nullptr的,但是一般非空情况下,是一定知道的。(虽然可以时而不知道对象,但是这个知道并不是对象直接的,而是类之间)
比如说,产品列表和产品类(订单列表和订单类) 是聚集关系。即使产品列表有时可以没有产品,但是它必须是“包含”
产品的,所以是聚合关系。 这里写聚集(聚集包括组合和聚合)
比如说,Friend和Member(Member和GroupMember)是继承关系,因为Friend(好友类)是QQ成员类的一种,所以是继承关系。
⑩用组合取代private继承
复用优先使用对象组合,而不是类继承。
⑩①函数返回值可以不接收!
比如scanf和printf都有返回值,main也有,但是不需要接受,所以,对于一些函数可以返回非void,必要时使用!
1+1;也没关系
⑩②inline 内联实现
内联永远是建议,建议放在头文件
内联会加快执行速度,直接展开函数代码
不足:内联有时会提高编译期依赖。
class A{
public:
void f() {} //默认是内联
inline void g() {}
inline void h();//无意义,只有在实现时声明inline才有意义
void k();
void T();
};
inline void A::k() {} //内联声明
void A::T() {} //外联实现
⑩③数组指针
int a[4]={1,2,3,4};
int (*p)[4]=&a;
/* p==&a; *p[0]==a[0];
⑩④函数重载
函数重载是可以的,调用的时候会调用最匹配的(可以在隐式类型转换下 转换就可以)。
class A{
public:
A() {}
int func (float a) {return 123;}
//int func(double b) {return 15;}
private:
A(const A& t)=delete;
};
int main(){
A a;
cout<<a.func(1.5d); 输出123
};
class A{
public:
A() {}
int func (float a) {return 123;}
int func(double b) {return 15;}
private:
A(const A& t)=delete;
};
int main(){
A a;
cout<<a.func(1.5); 输出15
};
⑩⑤浮点类型
在默认情况下,小数都被看做double型(所以上面会输出15),若使用float型小数,则需要在小数后面添加F或f。可以使用后缀d或D来明确表明这是一个double类型数据,不加d不会报错,但声明float型变量时如果不加f,系统会认为变量是double类型,从而出错。
⑩⑥运算符重载
只能重载一元和二元函数。
有些运算符只能用成员函数进行重载。(赋值函数只能成员函数重载)
有些运算符只能用自由函数进行重载。(<<和>>只能自由函数重载)
操作数至少有一个自定义类型 (如果不是自定义的,就是内置类型自己的运算符语义)
无法改变优先级
不能使用新的操作符