运算符重载——赋值
MyType b;
MyType a=b;//拷贝构造
a=b;//赋值,
class Fi {
public:
Fi() {}
};
class Fee {
public:
Fee(int){}
Fee(const Fi&){}
};
int main() {
Fee fee = 1;//Fee(int) defaulu ctor
Fi fi;
Fee fum = fi;//Fee(fi),直接用构造函数,不会发生赋值
fum = fi;//创造一个新的对象来赋值
return 0;
}
class Cargo {
public:
Cargo& operator=(const Cargo&) {
cout << "inside Cargo::operator=()" << endl;
return *this;
}
};
class Truck {
Cargo b;
};
int main() {
Truck a, b;
a = b;//printf inside Cargo::operator=()
return 0;
}
如果没有自己的,系统会调用自己的成员对成员的赋值
T& T::operator=(const T& rhs){
//check for self assignment
if(this!=&rhs){
//perfom assignment
}
return *this;
}
拿自己等自己一遍会有什么问题:
class A {
char *p;
A& operator=(const A& that)
{
delete p; //that和this相同的话,that.p就被delete掉了 多线程中空间会被迅速利用
p = new[strlen(that.p) + 1];
strcpy(p, that.p);
return *this;
}
};
有动态内存分配,指针的话就要写赋值的重载
如果全部是成员的话可以不写
当你的 类内部有你自己动态分配的内存的时候
你调用了默认的拷贝构造函数函数,这时候会将这个两个对象的中的动态分配的哪一块内存共享。但是这样做是及其危险的,析构会出现 double free 的错误。导致程序崩溃
最好的做法是对象拷贝只要涉及到动态内存的请自定义拷贝赋值函数,实现深拷贝
运算符重载——类型转化
类的作用是表达值的
看上去像基本元素类型
可以被函数传进传出
经常需要有重载的操作符,复数,乘法等
它的对象需要转化成其他一些类型,例如分数转化成double
C++中所有类都是小写字母开头的
如果成员变量只有一个别的类型的,则可以用这个值做成这个类的对象
也有用来做类型转换的运算符
class PathName{
string name;
public:
PathName(const string&);
~PathName();
};
...
string abc("abc");
PathName xyz(abc);
xyz = abc;//ok,先把abc做成类的一个对象,再调用重载的赋值操作
这个类没有自己的重载操作符,因此会调用default ctor,这是ok的,因为这个类里面没有指针,只有对象,会递归调用string的operator来构造一个新的string类
class One {
public:
One() {}//default ctor
};
class Two {
public:
Two(const One&) {}
};
void f(Two){}
int main() {
One one;
f(one);//想要two,却给了one,因此会先把one构造成two
return 0;
}
也可以防止程序自己这样构造对象
class PathName{
string name;
public:
explicit PathName(const string&);//只能做构造函数,不能用来自动转换类型
~PathName();
};
...
string abc("abc");
PathName xyz(abc);
xyz = abc;//error
explicit Two(const One&){}
就必须写f(Two(one))
;
构造了一个新的Two的对象
也可以做一个专用的类型转换函数
class Rational {
public:
...
operator double() const;//this -> double
};
Rational::operator double()const {
return numerator_ / (double)denominator_;
}
Rational r(1, 3); double d = 1.3*r;//r->double
前面没有返回类型,自己也没有参数,把X的对象转变成T
X::operator T()
built-in
primitive: char->short->int->float->double
->int->long
Implicit 对任意类型T,自动转
T->T& //初始化时是绑定,后面是重载的赋值
T&->T //赋值
T*->void*//不特定指向任何东西,关于T的类型信息删除掉,T所指的东西没有发生任何变化,通过指针看待内存的眼光变了
T[]->T* //数组交给指针
T* ->T[] //指针用数组的方法来管
T->const T//当作const
T->C的转化:
C(T),拿一个T可以做出C的对象来,C的构造
或者T类构造了一个operator(C),把自己变成T
class Apple {
public:
operator Orange() const;//Apple->Orange
};
class Orange {
public:
Orange(Apple);//Apple->Orange
};
void f(Orange){}
int main{
Apple a;//error 编译器不知道该用那个
return 0;
}
解决方法:在一个函数前加一个explicit
一般上来说,尽量别用类型转换函数,自动转换会有一些无意的错
所以写一个一看就能明白的,double toDouble() const
模板
列表
列表里面存放的类型可能不一样,共同基类
克隆代码,难于管理和维护
没有类型的list void* 但是这样不安全,任何东西都能放进去,不知道取出来的是什么类型
Templat:类型成为参数
函数模板,类模板,用模板做出来的函数,用模板做出来的类
类模板,模板类
sort function是常见的模板
模板类做容器
void swap(int&x, int&y) {
int temp = x;
int x = y;
int y = temp;
}
做成函数模板
template <class T> //只有T是可以换的,只有一个类型参数就用T,这行下面的东西就成了template
void swap(T&x, T&y) {
T temp = x; //可以是primitive type,也可以是user define type
x = y;
y = temp;
}
T可以作为传入传出的参数,也可以在函数内声明变量
template是声明,编译器把template记下来;
int i=3;int j=4;
swap(i,j);
float k=4.5;float m=3.7;
std::string s("Hello");
std::string t("world");
swap(s,t);//overloaded swap float是编译器自动做出来的
swap(int,int);//ok
swap(double,double)//ok
swap(int,double);//error 参数类型不同
使用模板时不能做类型转换
甚至连implicit的转换都是被忽略的
类模板
templat <class T>
void foo(void){}
foo<int>();
foo<float>();
template <class T>
class Vector { //整个Vector是模板,这是声明
public:
Vector(int);
~Vector();
Vector(const Vector&);
Vector & operator=(const Vector&);
T & operator[](int);
private:
T* m_elements;
int m_size;
};
int main() {
return 0;
}
Vector<int> v1(100);
Vector<Complex> v2(256);
v1[20] = 10;
v2[20]=v1[20] //ok if int->Complex defined
int -> complex 只有一种方法,complex有一个构造函数,以int作为输入,而且不是explicit的,不能用user-define是因为int是primitive的
类模板里的每一个函数都要是函数模板,后面可能用到T也可能用不到,每个类型都要用上尖括号
template <class T>
Vector<T>::Vector(int size) :m_size(size) {
m_elements = new T[m_size];
}
template <class T>
T& Vector<T>::operator[](int indx) {
if (indx < m_size && indx>0) {
return m_elements[indx];
}
else{...
}
}
模板II
template <class T>
void sort(vector<T>& arr) {
const size_t last = arr.size() - 1;
for (int i = 0; i < last; i++) {
for (int j = last; i < j; j--) {
if (arr[j] < arr[j - 1]) {
swap(arr[j], arr[j - 1]);
}
}
}
}
//调用之前的swap,也调用了重载的操作符[],arr[j]的类型是T,swap T,但是编译器不会做出一个swap的函数,因为这是声明,只会记录下来
出现了template后,C++的编译是多遍扫描的
vector<int> vi[4];
vi[0] = 4; vi[1] = 3; vi[2] = 7; vi[3] = 1;
sort(vi); //sort (vector<int> &)
vector<string> vs;
vs.push_back("Fred");
vs.push_back("Wilma");
vs.push_back("Barny");
sort(vs); //sort(vector<string> &) <需要做操作符的重载
Template里面可以有多个T
template<class Key,class Value>
class HashTable{
const Value& lookup(const Key&) const;
void install(const Key&,const Value&);
...
};
Vector<Vector<double *> > 后面的大于号不能连起来
Vector< int (*) (Vector<double>&,int)>
Vector里面的成员是一个函数指针,返回的是int,参数表第一项是一个引用,引用的Vector里面放的是double,第二项是个int
Template里面除了有T之外,还可以有其他参数的变量
template<class T,int bounds = 100>
class FixedVector {
public:
FixedVector();
T& operator[](int);
private:
T elements[bounds]; //fixed size array 种的时候bounds可以变
};
FixedVector<int,50> v1;
FixedVector<int,10*5> v2;
FixedVector<int> v3; //use default
template与继承
template<class A>
class Derived : public Base{...};
//类模板将来种下的每个类都是Base的子类
template<class A>
class Derived : public List<A>{...};
//继承的时候父类可以是一个Template种出来的类,Template之间是不能继承的,只能是具体种出来一个类才能拿来做继承
class SupervisorGroup : public LIst<Employee*>{...}
//某个类继承了某个模板种出来的类,这是一个实际的类,前面没有template,不是模板
v2和v3是同一个类型的吗
有一个template的h,写了f模板,两个cpp分别种f of <int>
,编译器同时只做一个编译类型,所以它们需要变名字或者合并,需要单独为C++做的link,所以不是一个好办法