我所知道的一个简单类
大家都说C++难学,我也正在学习,之所以难是可能因为没有了解C++中的一些很基础的东西,有点似懂非懂的感觉,而没有真正了解C++的运行机制,我一直在学习基础,这篇博客源于一个简单的类,这就是我所知道的一个简单类,博客中的观点大部分来至Effective C++和More Effective C++两本书,我是反复看了好几遍,还会一直看,因为我的C++还很菜。
一个简单的类源码:
#include <iostream>
using namespace std;
class UInt
{
public:
//缺省构造函数,在没有任何构造函数的时候,编译器默认生成缺省构造函数
UInt(){}
//这个构造函数可以把int类型隐式转换成UInt类型
//两种函数允许编译器进行类型转换:但参数构造函数和隐式类型转换运算符
UInt(const int& i):val(i)
{
cout<<"int"<<endl;
}
//拷贝构造函数
UInt(const UInt& i):val(i.val){
cout<<"UInt"<<endl;
}
//析构函数
~UInt(){
cout<<"~UInt"<<endl;
}
//赋值运算符
UInt& operator=(const UInt& i)
{
cout<<"operator="<<endl;
val=i.val;
return *this;
}
//其他的类型可以赋值给UInt类型,当然该报错的还是得报错(如你传入一个string类型),总比秘密做了很多你不想要的好吧
template<typename T>
UInt& operator=(const T& i)
{
cout<<"operator = other"<<endl;
val=i;
return *this;
}
//取址运算符
UInt* operator&()
{
return this;
}
//返回类型是一个常量指针,也就是说接收这个方法返回值的变量必须为常量指针
//这是一个常量方法,所以不能修改成员变量的值
const UInt* operator&() const
{
return this;
}
//返回当前对象的引用,当然你就可以对他进行任何合法的操作
//返回类型为引用类型或者指针类型就不会有临时变量的构造和析构
UInt& operator++()
{
*this+=1; //this是一个指针类型,*this为值类型,运算符左侧必须为值类型
return *this; //引用类型是对实际数据的引用,只能把值类型赋给引用类型,当然指针类型是不能赋给引用类型的
}
//返回的是一个局部变量,你对局部变量的任何操作都是没有意义的,所以返回类型为const
//大家都知道局部变量在超出他的作用域之后会自动析构,现在却返回一个局部变量,这里其实产生了一个临时变量,用oldVal初始化临时变量,
//若没有变量接收当前方法的返回值,方法调用完成之后,临时变量会自动析构,
//若有变量接收当前方法的返回值,临时变量在该变量自动析构之前析构,在栈中先定义后析构,这就是传说中的先进后出!
const UInt operator++(int)
{
UInt oldVal(*this);
++(*this);
return oldVal;
}
//加法运算,产生了一个局部变量uint,在函数返回时还会产生一个临时变量,程序要负责两个UInt类型的构造和析构,所以说+=比+效率高
const UInt operator+(const UInt i)
{
UInt uint(*this);
uint.val+=i.val;
return uint;
}
//UInt类型可以和char、short、int、float、double等类型进行加减乘除运算
template<typename T>
const UInt operator+(T t)
{
UInt uint(*this);
uint.val+=t;
return uint;
}
//返回当前对象,你可以对他进行任何合法的操作,所以他的类型为non-const
UInt& operator+=(const UInt& i)
{
val+=i.val;
return *this;
}
//UInt类型可以和其他基本类型(能和int类型进行+=运算的所有类型)进行+=运算
template<typename T>
UInt& operator+=(const T& i)
{
val+=i;
return *this;
}
//乘法 不是指针
const UInt operator*(const UInt& i)
{
cout<<"operator*"<<endl;
UInt uint(*this);
uint.val=uint.val*i.val;
return uint;
}
template<typename T>
UInt operator*(const T& i)
{
cout<<"operator* other"<<endl;
UInt uint(*this);
uint.val=uint.val*i;
return uint;
}
//乘法 不是指针
UInt& operator*=(const UInt& i)
{
val*=i.val;
return *this;
}
template<typename T>
UInt& operator*=(const T& i)
{
val*=i;
return *this;
}
//隐式类型转换,将UInt类型转换成int类型,当出现类型不匹配的时候,编译器会寻找可能进行匹配的类型转换,有时候这个隐式的类型转换可能不是你要的
//如UInt类型与int类型进行混合运算时,UInt类型会自动转换成int类型
operator int() const
{
return val;
}
private :
int val;
};
大部分的解释都在代码中,有兴趣的园友可以运行一下,应该会有收获的,那些输出的字符串告诉你他所在的函数被执行了。
一下是 陈太汉(http://www.cnblogs.com/hlxs/) 的部分观点:
1:尽量使用初始化而不要在构造函数里面赋值
创建对象分两步:
1):数据成员初始化
2):执行被调用构造函数的代码
用初始化列表的方式初始化成员变量,不用再构造函数内对成员变量进行赋值操作,不用初始化列表对成员变量进行赋值而在构造函数内进行赋值初始化,
初始化列表还是会执行,成员变量的缺省构造函数还是会被调用。静态成员变量只能被初始化,不能被赋值。
2:尽可能使用const
1):当参数类型不匹配时,用const修饰参数,编译器会寻找相关的类型转换方法,直到没有找到报错,有类型转换当然就有新变量的产生,
当然你的程序要承担他构造和析构得费用。没有const修饰时直接报错
2):它使被修饰的变量不被修改,意义重大,如加法运算符的返回值类型为const,可以防止程序员对一个加法表达式进行赋值。
3:指针跟指针所指的内容很容混淆,int i=5;int *p=&i;p=0表示将指针设为空,i的值还是5,指针p为空指针,不指向任何地址,*p=0表示将i的值修改0,指针p还是指向i。
4:组合运算符的效率比单一运算符高,++i的效率比i++高 ,请看代码中的解释
//返回类型是一个常量指针,也就是说接收这个方法返回值的变量必须为常量指针
//这是一个常量方法,所以不能修改成员变量的值
const UInt* operator&() const
{
return this;
}
//返回当前对象的引用,当然你就可以对他进行任何合法的操作
//返回类型为引用类型或者指针类型就不会有临时变量的构造和析构
UInt& operator++()
{
*this+=1; //this是一个指针类型,*this为值类型,运算符左侧必须为值类型
return *this; //引用类型是对实际数据的引用,只能把值类型赋给引用类型,当然指针类型是不能赋给引用类型的
}
//返回的是一个局部变量,你对局部变量的任何操作都是没有意义的,所以返回类型为const
//大家都知道局部变量在超出他的作用域之后会自动析构,现在却返回一个局部变量,这里其实产生了一个临时变量,用oldVal初始化临时变量,
//若没有变量接收当前方法的返回值,方法调用完成之后,临时变量会自动析构,
//若有变量接收当前方法的返回值,临时变量在该变量自动析构之前析构,在栈中先定义后析构,这就是传说中的先进后出!
const UInt operator++(int)
{
UInt oldVal(*this);
++(*this);
return oldVal;
}
//加法运算,产生了一个局部变量uint,在函数返回时还会产生一个临时变量,程序要负责两个UInt类型的构造和析构,所以说+=比+效率高
const UInt operator+(const UInt i)
{
UInt uint(*this);
uint.val+=i.val;
return uint;
}
//隐式类型转换,将UInt类型转换成int类型,当出现类型不匹配的时候,编译器会寻找可能进行匹配的类型转换,有时候这个隐式的类型转换可能不是你要的
//如UInt类型与int类型进行混合运算时,UInt类型会自动转换成int类型
operator int() const
{
return val;
}
作者:陈太汉