C++ 类定义
class Box
{
public:
double length; // 盒子的长度
double breadth; // 盒子的宽度
double height; // 盒子的高度
};
关键字 public 确定了类成员的访问属性。在类对象作用域内,公共成员在类的外部是可访问的。您也可以指定类的成员为 private 或 protected,这个我们稍后会进行讲解。
创建对象的4种方法
不用new关键字
在Stack栈里面分配空间,自动释放。
A a; //默认无参构造函数
A a(); //无参构造函数
A a = A(); //无参构造函数
A a = A(Param param); //有参构造函数
用new关键字
动态的,不确定分配空间大小
在heap堆里面分配空间,要手动释放(delete a或者delete a[],如果a是数组的话)
A* a = new A();
struct和类区别
C++ 中的 struct 对 C 中的 struct 进行了扩充,它已经不再只是一个包含不同数据类型的数据结构了,它已经获取了太多的功能。
struct 能包含成员函数吗? 能!
struct 能继承吗? 能!!
struct 能实现多态吗? 能!!!
既然这些它都能实现,那它和 class 还能有什么区别?
最本质的一个区别就是默认的访问控制,体现在默认的继承访问权限。struct是public的,class是private的。
成员函数
- 若将成员函数放到头文件中,则所有的成员函数都作为内联函数处理
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。在类定义中定义的成员函数把函数声明为内联的,即便没有使用 inline 标识符。
您也可以在类的外部使用范围解析运算符 :: 定义该函数,如下所示:
double Box::getVolume(void)
{
return length * breadth * height;
}
:: 叫作用域区分符,指明一个函数属于哪个类或一个数据属于哪个类。
:: 可以不跟类名,表示全局数据或全局函数(即非成员函数)。
继承
继承方式
有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。
1.public 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
2.protected 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
3.private 继承:基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
但无论哪种继承方式,上面两点都没有改变:
1.private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
2.protected 成员可以被派生类访问。
多继承产生冲突
使用类限定符,如s.Stu::Show()
C++ 类构造函数 & 析构函数
- 多态有个严重问题,(想去借助父类指针去销毁子类对象的时候去不能去销毁子类对象),因此要引入虚析构函数
使用初始化列表来初始化字段:
Line::Line( double len): length(len) { cout « “Object is being created, length = “ « len « endl; }
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序(比如关闭文件、释放内存等)前释放资源。
调用基类构造器的顺序按照他们继承时说明的顺序,而不是派生类构造函数中列举的顺序
- 多继承的构造函数顺序
- 虚拟基类按照被继承的顺序
- 非虚拟基类按照被继承的顺序
- 成员对象按照声明顺序
- 类自身构造函数
初始化列表
- 列表初始化是按照在类中的成员声明顺序来的,一定要注意
类对象的构造顺序是这样的:
1.分配内存,调用构造函数时,隐式/显示的初始化各数据成员;
2.进入构造函数后在构造函数中执行一般赋值与计算。
使用初始化列表有两个原因:
原因1.必须这样做:
《C++ Primer》中提到在以下三种情况下需要使用初始化成员列表:
情况一、需要初始化的数据成员是对象的情况(这里包含了继承情况下,通过显示调用父类的构造函数对父类数据成员进行初始化);
情况二、需要初始化const修饰的类成员或初始化引用成员数据;
情况三、子类初始化父类的私有成员;
原因2.效率要求这样做:
类对象的构造顺序显示,进入构造函数体后,进行的是计算,是对成员变量的赋值操作,显然,赋值和初始化是不同的,这样就体现出了效率差异,如果不用成员初始化类表,那么类对自己的类成员分别进行的是一次隐式的默认构造函数的调用,和一次赋值操作符的调用,如果是类对象,这样做效率就得不到保障。
赋值兼容规则
- 派生类对象可以赋值给基类对象
- 派生类对象可以初始化基类引用
- 派生类对象的地址可以赋给基类的指针
拷贝构造函数
几个原则:
C++ primer p406 :拷贝构造函数是一种特殊的构造函数,具有单个形参,该形参(常用const修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显示使用拷贝构造函数。当该类型的对象传递给函数或从函数返回该类型的对象时,将隐式调用拷贝构造函数。
C++支持两种初始化形式:
拷贝初始化 int a = 5; 和直接初始化 int a(5); 对于其他类型没有什么区别,对于类类型直接初始化直接调用实参匹配的构造函数,拷贝初始化总是调用拷贝构造函数,也就是说:
A x(2); //直接初始化,调用构造函数
A y = x; //拷贝初始化,调用拷贝构造函数
必须定义拷贝构造函数的情况:
只包含类类型成员或内置类型(但不是指针类型)成员的类,无须显式地定义拷贝构造函数也可以拷贝;有的类有一个数据成员是指针,或者是有成员表示在构造函数中分配的其他资源,这两种情况下都必须定义拷贝构造函数。
什么情况使用拷贝构造函数:
类的对象需要拷贝时,拷贝构造函数将会被调用。以下情况都会调用拷贝构造函数:
(1)一个对象以值传递的方式传入函数体
(2)一个对象以值传递的方式从函数返回
(3)一个对象需要通过另外一个对象进行初始化。
拷贝构造函数是一个对象初始化一块内存区域,这块内存就是新对象的内存区,而赋值函数是对于一个已经被初始化的对象来进行赋值操作。
浅拷贝和深拷贝
和java类似,浅拷贝中含有指针类的数据会指向同一内存。而对同一内容空间的两次释放会引起运行错误。所以要对指针类型数据重新new
C++ 友元函数
类的友元函数是定义在类外部,但有权访问类的所有私有(private)成员和保护(protected)成员。尽管友元函数的原型有在类的定义中出现过,但是友元函数并不是成员函数。
友元可以是一个函数,该函数被称为友元函数;友元也可以是一个类,该类被称为友元类,在这种情况下,整个类及其所有成员都是友元。
如果要声明函数为一个类的友元,需要在类定义中该函数原型前使用关键字 friend
#include <iostream>
using namespace std;
class Box
{
double width;
public:
friend void printWidth( Box box );
void setWidth( double wid );
};
// 成员函数定义
void Box::setWidth( double wid )
{
width = wid;
}
// 请注意:printWidth() 不是任何类的成员函数
void printWidth( Box box )
{
/* 因为 printWidth() 是 Box 的友元,它可以直接访问该类的任何成员 */
cout << "Width of box : " << box.width <<endl;
}
// 程序的主函数
int main( )
{
Box box;
// 使用成员函数设置宽度
box.setWidth(10.0);
// 使用友元函数输出宽度
printWidth( box );
return 0;
}
类静态变量
静态成员变量在类中仅仅是声明,没有定义,所以要在类的外面定义,实际上是给静态成员变量分配内存。如果不加定义就会报错,初始化是赋一个初始值,而定义是分配内存。
// 初始化类 Box 的静态成员 其实是定义并初始化的过程
int Box::objectCount = 0;
//也可这样 定义却不初始化
//int Box::objectCount;
const
常对象
class const obj或者const class obj
常数据成员
构造函数只能通过初始化列表对常数据成员初始化
常成员函数
type func(para) const;
是函数的一部分,在声明和定义的时候都要有关键字const
常成员函数可以访问普通数据成员和常数据成员及常对象的数据成员,而普通成员函数可以访问常数据成员,但不可以改变值,也不能访问常对象的数据成员
常成员函数是常对象唯一的对外接口
常成员函数不能调用普通成员函数