C++ 类的全面理解
class A
{
public:
A(int b):m_public(b),m_protected(1), m_private(2){}
int m_public;
protected:
int m_protected;
private:
int m_private;
};
int main( void )
{
A a(10);
cout<<a.m_public<<endl; //正确,可以直接调用
cout<<a.m_protected<<endl; //错误,不可以直接调用
cout<<a.m_private<<endl;//错误,不可能直接调用
}
而子类对父类的继承类型也有这三种属性,分别为公开继承,保护继承和私有继承。
class A
{
public:
A(int b):m_public(b),m_protected(1), m_private(2){}
int m_public;
protected:
int m_protected;
private:
int m_private;
};
class B: public A{} //公有继承
class C: protected A{} //保护继承
class D:private A{} //私有继承
父类成员的公开属性有三种,子类的继承属性也有同样的三种,那么一共就有九种搭配。两个里取严格的那一种
public | protected | private | |
public继承 | public | protected | 不可用 |
protected继承 | protected | protected | 不可用 |
private继承 | private | private | 不可用 |
2.
class A
{
public:
double m_a;
};
int main( void )
{
A a; //调用默认无参构造函数,此时m_a的值是不确定的,不能用
//离开主函数前调用析构函数,释放a的内存
}
不会再生成默认的无参构造函数
class A
{
public:
A(int i):m_a(i){}
int m_a;
};
int main( void )
{
A a; //错误!没有无参构造函数
A a1(5); // 调用了A中程序员定义的有参构造函数
A a2(6); // 调用了A中程序员定义的有参构造函数
A a3 = a1; //此处调用默认的拷贝构造函数
a2 = a1; //此处调用默认的赋值函数
}
上面的程序中尤其需要注意的是 A a3 = a1;
这一句,虽然有等号,但是仍然是拷贝构造函数。拷贝构造函数和赋值函数的区别在于等式左边的对象是否已经存在。a2 = a1;
这一句执行的时候,a2已经存在,因此是赋值函数,而执行A a3 = a1;
这一句的时候,a3还不存在,因此为拷贝构造函数。
这一点当类中存在指针成员和静态成员变量的时候就非常危险
class A
{
public:
A(int i, int* p):m_a(i), m_ptr(p){}int m_a;
int *m_ptr;
};
int main( void )
{
int m = 10, *p = &m;
A a1(3, p);
A a2 = a1; //a2 和 a1的m_ptr都指向了同一地址
*p = 100;
cout<<*(a2.m_ptr)<<endl; //输出为100
}
这也就是C++中由于指针带来的浅拷贝的问题,只赋值了地址,而没有新建对象。因此假如类中存在静态变量或者指针成员变量时一定要自己手动定义赋值、拷贝构造、析构函数。
class A
{
public:
A(int b):m_public(b){}
int m_public;
};
class B:public A
{
};
int main()
{
B b; //出错,因为父类没有无参构造函数
}
因此在这种情况必须要显示定义子类的构造函数,并且在子类构造函数中显示调用父类的构造函数。
class A
{
public:
A(int b):m_public(b){}
int m_public;
};
class B:public A
{
public:
B(int num):A(num){}
};
int main()
{
B b1; //出错,由于父类没有无参构造函数,因此B也不存在无参构造
B b2(5); //正确
}
- 假如把类的析构函数定义为私有,那么就无法在栈中生成对象,而必须要通过new来在堆中生成对象。
- 另外在这里提及一点,对应的,如何让类只能在栈中生成,而不能new呢?就是将new 和delete重载为私有。
class A
{
public:
int m_a;
A(int num):m_a(num){}
};
class B
{
public:
const int m_const;
int &m_ref;
A a;
B(int num, int b):m_ref(b), m_const(1), a(num) //初始化列表
{
cout<<"constructing B"<<endl;
}
};
int main()
{
int n = 5;
B b(1, n);
}
成员函数的重载
- 函数名字相同;
- 参数不同 ,也可以仅仅是顺序不同;
- virtual 关键字可有可无;
class A
{
public:
int m_a;
A(int num):m_a(num){}
void show(int n){} // (1)
virtual void show(int n){} // (2) 错误!!不是重载,重复定义了(1),因为virtual关键字不能重载函数
void show(double d){} // (3)show函数的重载
void show(int a, double b){} // (4)show函数的重载
void show(double b, int a){} // (5)show函数的重载
void show(int a, double b) const {} // (6)show函数的重载,const关键可以作为重载的依据
void show(const int a, double b){} // (7)错误!!不是重载, 顶层const不可以作为重载的依据,重复定义了(6)
void show(int *a){} // (8)show函数的重载
void show(const int *a){} // (9)show函数的重载,只有底层const才可以作为重载的依据
void show(int * const a){} // (10) 错误!!不是重载,重复定义了(8),因为这里也使用了顶层const
};
成员函数的隐藏,这里“隐藏”是指派生类的函数屏蔽了与其同名的基类函数。
- 只要子类函数的名字与基类的相同,那么不管参数相同与否,都会直接屏蔽基类的同名函数。
class A
{
public:
void show(int a) {cout<<"A::show()"<<endl;}//(1)
};
class B:public A
{
public:
void show(){cout<<"B::show()"<<endl;} //(2)将(1)屏蔽了
};
class A
{
public:
void show(int) {cout<<"A::show()"<<endl;}
};
class B:public A
{
public:
using A::show;
void show(){
show(0); //一定要在前面显式声明using A中的show函数,否则此句会编译错误
cout<<"B::show()"<<endl;
}
};
int main()
{
B b;
b.show();
}
- 不同的范围(分别位于派生类与基类);
- 函数名字相同;
- 参数相同 ;
class A
{
public:
A(int n):m_a(n){}
friend class B; //声明B为A的友元类
private:
int m_a;
};
class B
{
public:
B(A a){cout<<a.m_a<<endl;} //由于B是A的友元类,所以可以直接调用a的私有m_a成员
};
int main()
{
A a(1);
B b = B(a); //输出1
}
就如名字定义的那样,只是朋友,不具有任何亲属关系,因此无法使用this指针进行调用。
友元函数常用在重载运算符。因为通常重载运算符的时候都要用到私有变量,所以用友元函数来重载是非常合适的。
4.
首先要明确,有6个运算符是不可以被重载的。
.
(成员访问运算符);.*, ->*
(成员指针访问运算符);::
(域运算符);sizeof
(长度运算符);?:
(条件运算符);
-
=, [] ,() ,-> 四个符号只能通过成员函数来重载,不能通过友元函数来定义。因为当编译器发现当类中没有定义这4个运算符的重载成员函数时,就会自己加入默认的运算符重载成员函数。而如果这四个运算符写成友元函数时会报错,产生矛盾。
-
不允许用户定义新的运算符作为重载运算符,不能修改原来运算符的优先级和结合性,不能改变操作对象等等限制。
重载原则如下:
- 如果是一元操作,就用成员函数去实现;
- 如果是二元操作,就尽量用友元函数去实现;
- 如果是二元操作,但是对两个操作对象的处理不同,那么就尽可能用成员函数去实现;
运算符的重载:
class A
{
public:
A(int n):m_a(n){}
int m_a;
friend A operator+(A const& a1, A const & a2);
};
A operator+(A const& a1, A const & a2)
{
A res(0);
res.m_a = 1 + a1.m_a + a2.m_a;
return res;
}
int main()
{
A a1(1), a2(2);
A a3 = a1 + a2;
cout<<a3.m_a; //输出4
}
5. 类中的const关键字
class A
{
public:
A(int n):m_a(n){}
int m_a;
void show(){cout<<"A::show()"<<endl;}
};
int main()
{
A a1(1);
const A a2(2);
a1.show(); //正确
a2.show(); //错误
}
假如增加const函数后,就可以正常运行。
class A
{
public:
A(int n):m_a(n){}
int m_a;
void show(){cout<<"A::show()"<<endl;}
void show() const{cout<<"A::show() const"<<endl;}
};
int main()
{
A a1(1);
const A a2(2);
a1.show(); //正确 输出A::show()
a2.show(); //正确 输出A::show() const,自动调用const函数
}
class A
{
public:
A(int n):m_a(n){}
int m_a;
void changeValue(int *p){cout<<"changeValue"<<endl;}
void changeValue2(int const *p){cout<<"changeValue2"<<endl;}
};
int main()
{
int n = 10;
int const *p_n_const = &n;
int *p_n = &n;
A a(1);
a.changeValue(p_n_const); //错误,无法把int const*类型,转成int *类型,但是反之可以
a.changeValue2(p_n); //正确,输出changeValue2
}
表示指向的对象不能改变,所以如果传入A实参,只要不改变对象的值就不会有问题。
但是如果形参为A,则有可能改变A指向的对象,这是const A办不到的,所以编译器不允许传入const A