10.运算符重载、Effectiva等

1. C++会自动生成的成员函数

  • 如果程序使用对象的方式要求这样做,而用户没有自己定义如何做,那么编译器将会生成下列函数的定义:

(1)默认构造函数:
默认构造函数要么没有参数,要么所有的参数都有默认值,如果没有定义任何构造函数,编译器将定义默认构造函数,下列情况将使用默认构造函数

Star rigel; 
Star pleiades[6]; 

(2)默认析构函数:
若没有定义析构函数,则编译器将定义默认析构函数,默认析构函数不执行任何操作,==若类动态申请内存,则必须定义析构函数 ==
(3)拷贝构造函数
拷贝构造函数接收器所属类的对象作为参数,如Star类的拷贝构造函数如下:

Star(const Star & s); 

如果程序没有使用拷贝构造函数,编译器将提供原型,但不提供函数定义;否则程序将定义一个执行成员初始化的拷贝构造函数,新对象的每个成员都被初始化为原始对象的相应成员的值,如果成员为类对象,则初始化该成员时将使用相应类的拷贝构造函数。通常,使用new初始化的成员指针通常要求执行深复制,在这种情况下需要定义自己的拷贝构造函数 ;

以下情况,将使用拷贝构造函数:
将新对象初始化为一个同类对象
按值将对象传递给函数
函数按值返回对象
编译器生成临时对象

  • 赋值运算符:
    a.编译器不会生成将一种类型赋值给另一种类型的赋值运算符 ;
    b.默认赋值为成员赋值,如果成员为类对象,则默认成员赋值将使用相应类的赋值运算符 ;
    c.默认的赋值运算符用于处理同类对象之间的赋值,如果语句创建新的对象,则使用初始化,如果语句修改已有对象的值,则是赋值 ;
Star sirius; 
Star alpha = sirsus;     //这是初始化,创建了新的对象 
Star dogstar; 
dogstar = sirius;     //这是赋值,没有创建新的对象  

d.如果希望能将字符串赋给Star对象,一种方法是显示定义如下运算符:

Star & Star::operator=(const char *){  ……  } 

e.另一种方法是使用转换函数,将字符串转换为Star对象,第一种方法运行速度快,但使用的代码教唆,而使用转换函数可能导致编译器出现混乱 ;

  • 地址运算符:

C++11提供了另外两个特殊成员函数:
移动构造函数、移动赋值运算符
C++11引入了新的关键字用于表示空指针:nullptr
NULL是C语言的指针宏;

2.运算符重载

不要重载&&和||操作符,因为无法实现短路规则;
= ,[] , ()和->操作符只能通过成员函数进行重载
<<和>>操作符最好通过友元函数进行重载

  • 重载++ --操作符:
    a.为了区分前置++与后置++的区别,C++规定了后置++/–操作时,在operator ++或operator --后面的括号里加上一个int占位符,用以区别前置与后置的差异;如

后置++或–重载:

Type Type::operator ++ (int);
Type Type::operator -- (int);

前置++或–重载:

Type& Type::operator ++();
Type& Type::operator --();
  • C++的友元函数:
    声明一个Time类,Time类重载*运算符:
  1. Time operator* (const Time &t)
    这种情况:
语法意义
Time t1,t3;
t1=t1*t2;编译器将解释为:t1.operator*(t2);
t1=t2*t1;编译器将解释为:t1.operator*(t2);
  1. Time operator* (double mult);
    这种情况:
语法含义
Time t1;
double n;
t1=t1*n;编译器将解释为:t1.operator*(n);
t1=n*t1;这样不可行:需要使用友元函数进行运算符重载,重载函数原型如下:friend Time operator*(double m,const Time & t); 重载该函数后,编译器将解释为:t1=operator*(n,t1); 也可以将该函数不重载为友元函数:Time operator*(double m,const Time &t) {return *m;}
  • Time类重载<<运算符:
    (1)若使用Time的成员函数重载<<运算符,那么Time对象将是第一个操作数,这意味着必须这样使用<<: t1<<cout;
    (2)因此必须使用友元函数重载<<运算符,声明如下:
friend void operator<<(ostream & os,const Time & t);

(3)前面重载的运算符不能这样使用:cout<<t1<<“Displayed”,因为重载的<<运算符没有返回值,不能这样连续使用,需要修改为:

friend osstream & operator <<(ostream & os,const Time &t);

注意:在重载<<运算符的时候,重载函数的第一个操作数必须是osstream &类型;

  • C++不能重载的运算符:
符号名称
.成员访问运算符
.*成员指针访问运算符
::域运算符
sizeof长度运算符
?:条件运算符
typeid一个RTTI 运算符
const_cast强制类型转换符运算符
dynamic_cast强制类型转换运算符
reinterpret_cast强制类型转换运算符
static_cast强制类型转换运算符

3.指针

  1. scoped_ptr:这是最常用的智能指针,当你new一块内存后把内存地址交给scoped_ptr管理,这样就不用显示调用delete了,当离开作用于后,该内存会自动被释放,如:
int *p =new int ;
scoped_ptr<int> scoped_int_ptr(p);

注意无需在delete p;这样会double free;另外一个重要的点时:scoped_ptr不允许传递指针,即它的拷贝构造函数和赋值函数都是private的,不允许调用;

scoped_ptr<int> scoped_int_ptr2=scoped_int_ptr;//不允许
  1. auto_ptr: 和scoped_ptr的用法基本一致,但是它允许指针传递;当发生赋值时,原对象的指针会转移给新对象,这时候原对象的指针就为NULL了;不能再调用了;
  2. shared_ptr: shared_ptr一样包装了new操作符在堆上分配的动态对象,但是它实现的是引用引用计数型的智能指针;可以被自由地拷贝和赋值,在任意的地方共享它,当没有代码使用(引用计数为0)时,才删除被包装的动态分配的对象;但是,shared_ptr注意不要有循环引用,否则会出现内存泄露(两者不能自动释放);
  3. weak_ptr:它是被std::shared_ptr管理的对象存在非拥有性(“弱”)引用。在访问所引用的对象前必须先转换为std::shared_ptr。此外std::weak_ptr还可以用来避免std::shared_ptr的循环引用;
  • const指针
    有以下表达式:
int a=248,b=4;
int const c=21;
const int *d=&a;
int *const e=&b;
int const* const f=&a;
  • 看如何用const:
    如:
int const * const p;
//找到第一个const,后面除了有个const之外,就是* 和p,
//const * p就是我们想要的结果,p指向的内容不可变;
//找到第二个const,后面只有个p,那么:
//const p,这就是结果,p不可变;

C++规则总结如下:找到const,在看后面除了const的内容;是什么东西,那个东西就不可变;

  • const语法虽然变化多端,但是并不高深莫测:

    a.如果关键字const出现在星号左边,表示被指物是常量;
    b.如该出现在星号右边,表示指针自身是常量;
    c.如该出现在星号的两边,表示被指物和指针都是常量;

如果被指物是常量,有些程序员会将关键字const写在类型之前,有些人会把它写在类型之后、星号之前。两种写法的意义相同,所以下列两个函数接受的参数类型是一样的:

void f1(const Widget* pw); //f1获得一个指针,指向一个常量的(不变的)Wideget对象;
void f2(Widget const *pw);  //f2也是;
  • C++函数声明的时候后面加const

    非静态成员函数后面加const(加到非成员函数或静态成员后面会产生编译错误),表示成员函数隐含了传入的this指针为const指针;决定了在该成员函数中,任意修改它所在的类的成员的操作都是不允许的(因为隐含了对this指针的const引用):
    唯一的例外是对于mutable修饰的成员。加了const的成员函数可以被非const对象和const对象调用;但是不加const的成员函数只能被非const对象调用;

  • 函数指针
    函数指针的定义:
    a.通过函数类型定义函数指针:FuncType* pointer
    b.直接定义:type(*pointer)(paramenter list)
    ==
    pointer为函数的指针名;
    type为指向函数的返回值类型;
    paramenter list为指向函数的参数类型列表
    ==
    函数指针数组:

char * (*pf[3])(char * p);

函数指针数组的指针:

char *(*(*pf)[3])(char * p);
- 复杂指针的阅读技巧 右左法则: 1.从最里层的园括号中未定义的标识符看起; 2.首先往右看,再往左看 3.当遇到园括号或者方括号时,可以区诶的嗯部分类型,并调转方向; 4.重复2.3步骤,直到结束; [C语言指针的理解](https://www.cnblogs.com/haore147/p/3647262.html)

4.Effective C++

1.对象初始化:
既然这样,为避免在对象初始化之前过早地使用它们,你需要做三件事
第一:手工初始化内置型non-member对象。
第二:使用成员初值列(member initialization lists)对付对象的所有成分。
最后:在“初始化次序不确定性”(这对不同编译单元所定义的non-local static对象是一种折磨)氛围下加强你的设计。
请记住

  • 为内置对象进行手工始化,因为C++不保证初始化它们。
  • 到构造函数最好使用成员初值列(member initialization list),而不要在构造函数本体内使用赋值操作(assignment)。初值列列出的成员变量,其排列次序应该和它们在class中的声明次序相同。
  • 为免除“跨编译单元之初始化次序”问题,请以1alstatic对象替换non-local static对象。

5.C++内存管理

内存分为5个区:
1.:函数局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放,栈效率最高,但容量有限;
2.:由new分配的内存块,它们的释放编译器不去管,由我们的应用程序去控制,一般一个new就要对应一个delete,果程序员没有释放掉,那么在程序结束后,操作系统会自动回收;
3.自由存储区:由malloc等分配的内存块,它和堆是十分相似的,不过它是用free来结束自己的生命;
4.==全局\静态存储区:==全局变量和静态变量被分配到同一块内存中,在以前的C语言中,全局变量又分为
初始化和未初始化的,在C++里面没有这个区分了,他们共同占用同一块内存区 ;
5.常量存储区:里面存放的是常量,不允许修改;

堆和栈的区别:
1.管理方式的不同:
a.栈:由编译器自动管理,无需我们手工控制;
b.堆:释放工作由程序员控制,容易产生memory leak;
2.空间大小不同:
a.栈:对于栈来讲,一般都属于一定的空间大小;
b.堆:一般来讲在32位系统下,堆内存可以达到4
G的空间,从这个角度来看堆内存几乎没有什么限制;
3.能否产生碎片不同:
a.栈:不会产生,因为栈先进后出的队列,在一个元素弹出之前,在它上面后进的栈内容已经被弹出;
b.堆:频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使得程序效率降低;
4.生长方向不同:
a.栈:它的生长方向是向下的,比如局部变量;
b.堆:生长方向是向上的;也就是向着内存地址增加的方向;
5.分配方式不同;
a.栈:静态分配和动态分配
i. 静态分配是编译器完成的,比如局部变量的分配;
ii. 动态分配由alloca函数进行分配;
iii. 栈的动态分配和堆是不同的,它的动态分配由编译器进行释放,无需我们手动实现;
b.堆:堆都是动态分配,没有静态分配的堆;
6.分配效率不同:
a.栈效率高:栈是机器系统提供的数据结构,计算机会在底层对栈提供支持;
b.堆效率低:堆是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回 ;

  • 设置一个类,禁止其产生栈对象:
    1.析构函数声明为protected,重新声明一个方法析构该对象:
class NoStackObject{
protected:
	~NoStackObject(){}
public:
	void destroy(){
		delete this;///调用保护析构函数
	}
}
  1. 构造函数和析构函数都设置为private,声明一个静态方法来创建新的对象,并重新声明一个方法进行析构:
class NoStackObject
{
protected:
	NoStackObject(){}
	~NoStackObject(){}
public:
static NoStackObject* creatlnstance()
{
	return new NoStackObject();//调用保护的析构函数
}
void destroy()
{
	delete this;//调用保护的析构函数
}
}
  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值