- 将类定义在头文件之中,而将类方法的具体实现放在源文件中。
- 在函数实现中通过作用域运算符::表明所属类。
class Stock
{
private:
int n;
void show(int s);
...
}
void Stock::show(int s)
{
...
}
- 定义位于类声明中的函数自动成为内联函数,在类外面的函数也可以使永inline成为内联函数。
构造函数与析构函数
- 构造函数无返回值,函数名必须和类名保持一致。
- 为构造函数提供重载版本,或者提供默认值,使得支持多样化赋值。
- 构造函数还可以用于创建临时对象。
- 析构函数只能有一个,并且如果要使用new,必须提供析构函数。
class Stock
{
...
Stock() = default;
Stock(const std::string& os, int n, double p);
~Stock()
{
cout << "bye";
}
}
Stock::Stock(const std::string& os, int n, double pri)
{
company = os;
shares = n;
share_val = pri;
}
在函数命名后面添加const,使得函数不对隐式对象进行任何修改。
void show(int n) const
{
cout <<company <<" ..."<<n<<endl;
}
//可以对n进行修改,但不会对调用该函数的对
//象进行任何修改
认识this指针
const Stock& Stock::Stock_cmp(const Stock& a)const
{
if (a.shares > shares)return a;
else return *this;
}
- this指针是指向调用当前函数的对象的地址。
在类内定义常量,使所有对象共用。用来创建数组
- 使用enum枚举
class Test
{
enum{time = 60}
}
- 使用static关键字
static const int time =60;
下面不支持的
const int time =12;
//未创建对象前,未分配内存。
运算符重载
operation 运算符(参数表)
class Node
{
int x,y;
public:
Node operator+(const Node &a)const
}
Node Node::operator+(const Node &a)const
{
Node c;
c.x = x + a.x;
c.y = y + a.y;
return c;
}
操作者的左侧是函数的调用者。
重载运算符的限制:
- 重载的运算符必须要有一个操作数是用户自己定义的类型.
- 使用运算符不能违背句法规则。即一元运算符不能变成二元运算符,反之亦是。
- 不能修改运算符优先级。
- 不能创造运算符。
- 不能重载以下运算符:
- sizeof
- 成员运算符 .
- ::,作用域运算符
- ?:,条件运算符
- typeid,RITI运算符
- const_cast、dynamic_cast、reinterpret_cast、static_cast四个强制类型转换符号
下面的运算符必须作为成员函数重载
= 赋值, []下标,()函数调用,->指针成员
友元
赋予函数以类成员函数的权限
不能通过成员运算符 . 来访问
必须在类中声明
友元函数不是成员函数,没有this指针。
class Name
{
...
friend Name MyFunction(...)
}
Name MyFunction(...)
{
...
}
重载 <<
1.(不推荐)
class Test
{
...
friend void operator<<(ostream &os,const Test &a)
{
os << "asdfad"<< a....
}
}
但是我们可以发现这不能用于以下表达
cout << “asdf” << a <<endl;
所以我们可以把返回值设为 ostream &;
class Test
{
...
friend ostream & operator<<(ostream &os,const Test &a)
{
os << a...;
}
}
类的自动转换和强制类型转换
类使用构造函数来实现隐式的类型转换。
class Test
{
...
public:
Test(int n)
{
...
}
}
Test n;
n = 10;
//因为Test提供了关于int的构造函数,
//那么int就可以转换为Test类型。
//有时候我们不想要隐式的类型转换,可以使用
//explicit来修饰构造函数。
explicit Test::Test(int n)
{
...
}
使用explicit关键词限制隐式类型转化。
- 构造函数只能实现到类类型的转化,类转化为其他类型数据要用转换函数。
- 格式: operator typename ();
注意:
- 转换函数必须是类方法
- 不能指定返回类型
- 不能有参数
转换为double:
operator double()
{
...
}
- 转换函数也可以使用explicit来限制必须显示转换,甚至建议这么做,避免不经意的错误。
类的动态内存分配:
将类成员设为指针,在构造函数是用new分配内存,析构函数释放内存。
当使用对象来初始化另一个对象的时候,编译器执行默认复制构造函数。默认构造函数逐个复制非静态元素。如果成员函数是类,优先调用复制构造函数,如果没有那么执行默认构造函数。
People Jane(Jack);
People Jane = Jack;
People Jane = People(Jack);
People *Jane = new People(Jack)
以上都会调用复制构造函数
显式定义复制构造函数
People::People(const People &person)
{
age = person.age;
....
}
如果对象是指针的话,默认构造函数很可能会释放两次内存,导致程序崩溃。
class People
{
char * name
...
pulic:
People(const char * s);
~People();
}
People::People(const char *s)
{
int len = strlen(s);
name = new char[len+1];
name = s;
}
People::~ People()
{
delete [] name;
}
使用默认构造函数,会将name的地址传递,那么调用析构函数时候就会将该处地址释放两次,引起错误。
默认的赋值运算符也是逐个复制非静态成员,显然也可以出现这样的错误。因此我们也要重载赋值运算符 =
如果不愿意写复制构造函数,又不愿意使用默认复制或者赋值方法,可以显式的将他们定义为private私有的,这样就禁止使用。
People & People::operator =(const People &s)
{
int len = strlen(s.name);
delete [] name;
name = new char [ len + 1];
name = strcpy(name,s.name);
...
return *this ;
}
在构造函数中使用new,所有构造函数的new必须一致,因为析构函数只有一个,所有要么使用delete [] ,要么是delete,而构造函数必须和它匹配。
- 类构造函数初始化列表;
class People
{
static int Size ;
People(int siz,...)
}
People::People(int siz):Size(siz)
{
...
}
//因为static在所有对象中只初始化一次,所以必须使用这样的初始化。
//或者显式
类继承
公有继承
class Chinese:public People
{
}
关系
- 派生类可以使用基类的方法。派生类不能直接使用基类的私有成员,而通过基类的公有类方法访问基类的私有成员。
- 基类指针可以不进行显式转化下指向派生对象;
- 基类引用可以不进行显式转化下引用派生类对象。
注意:
- 派生类在使用构造函数初始化时候,必须使用基类的构造函数初始化基类成员,而不能直接为基类成员赋值。
- 公有继承用于实现一种 is-a-kind-of,简称is-a的关系。
多态公有继承
如果希望一个方法在基类和派生类的行为不一致,即方法行为取决于方法对象。
- 通过在派生类中重新定义基类的方法实现。
- 使用虚函数。
- 在类中声明函数时,在函数返回值之前添加virtual,使函数成为虚函数。
不使用虚函数,将会通过判断指针和引用的类型来调用相应的类方法,而使用虚函数之后,将通过判断指针和引用指向的对象类型调用相应类方法。
//Chinese为People派生类。
People Jane(...);
Chinese CF;
People &P1 = Jane;
People &p2 = CF;
P1.age();
P2.age();
//如果不是虚方法,那么将调用People的age方法,
//如果定义为虚方法,那么P1将调用People的age,P2调用Chinese的age。
动态联编与静态联编
将代码中的函数调用解释为执行特定的函数代码块称为函数名联编(blinding)。在编译时候进行联编的称为静态联编,然而虚函数的存在使得静态联编变得困难,因而引入了与之对应在程序执行是进行联编的动态联编。
- 在基类声明中使用virtual,可以使得该方法在所有派生类和基类都是虚的。
- 构造函数不能是虚函数,但析构函数应当是虚函数。
- 在派生类中重新定义类方法将覆盖基类的方法,所有会有以下经验规则:
- 重新定义方法应当确定与原来的原型相同,但如果返回值是基类的引用或者指针,可以修改为派生类的引用与指针。
- 如果基类的方法是重载了,那么应该重新定义所有重载版本。避免其余版本被隐藏。
访问控制protected
protected成员与私有成员private类似,只有当涉及到派生类时才会有所不同。派生类的成员可以直接访问基类的保护成员protected,但不能访问基类的私有成员。
继承与动态内存分配(小技巧)
- 当派生类不使用new的时候,不需要显式的为派生类定义析构函数、复制构造函数和赋值运算符。
- 如果派生类使用了new,那么就必须显式定义。上述函数。
- 在继承的时候,构造函数和析构函数,以及赋值运算符不会被继承。