1.C++最重要的特性之一——类,该如何定义呢?
类,作为面向对象编程的基础,定义由struct或class开始。
如:
class A
{
};
注意:最后的花括号要加分号
2.class 和struct的区别
class后成员,若不加访问说明符,默认private(继承也是如此),struct则为public。
class A{
int a;//默认private
};
struct B{
int b;//默认public
};
3.构造与析构
类都有一个构造与析构函数。其返回值为类类型。这两个函数必须为public的。如果不说明,编译器将隐式生成默认版本。
显式要求如下:
class A{
public://必须!
A()=default;//类名+括号+=default
~A()=default;//多加一个
};
4.类与作用域
有以下代码:
class A{
public:
ostream& print(ostream&os,string as);
};
class B{
public:
ostream& print(ostream&st,string bs);
};
虽然都可简写为:
ostream& print(ostream&,string);
但是,每个类实际上是一个作用域。
所以,两者是有区别的。如下代码:
int main()
{
A a;
B b;
a.print(cout,"Hello!");//是A::print
b.print(cout,"Hello!");//是B::print
}
4.新的访问控制符
除了public和private以外,还有一种访问控制符,就是protected。protected介于public和private中间。public:派生类,类内,类外,友元均可访问。private:类内,友元可访问,其余不可。protected:类内,派生类(无论继承的是什么,只要继承该类),友元可访问。例子:
class A{
private:
int a;
protected:
int b;
public:
int c;
};
class B:public A{
public:
int d;
};//B成员可访问b,c
class C:private A{//private可写可不写(默认)
public:
int e;
};//C成员可访问a,b,c
5.this 指针
this指针是类内的一个隐式(也可使用)指针。所以,你可以返回它。如:
#include<iostream>
using namespace std;
class A{
public:
A& where(char);
char b;
};
inline A::A& where(char a)
{
//因为在类的作用域内,所以无需用 .来访问。
b=a;
return *this;//返回this
};
6.静态成员
静态成员不属于任何其他成员。此外,静态成员还可以是不完全类型。如:
class A{
public:
//...
private:
static A type;
A *you;
A error;//错误:数据成员必须为完全类型
};
7.构造函数(二)
构造函数除了有默认形式
class A{
public:
A()=default;
};
以外,还有其它形式,如分别用给定初始值初始化。如:
class A{
public:
float a;
int b;
A(float c): a(c),b(c){}//为空
};
也有其它的,如:
class A{
public:
int a;
A() {}
};
8.union——节省空间的类(一)
union是一种特殊的类。它的成员中每个时刻都只有一个成员是已知的。若一个成员变为已知的,则其它成员变为未知状态。union 默认形式同struct。如:
union A{
//默认公有
int a;
string b;
float c;
};
9.友元
友元可以访问类的任何成员。定义友元以friend 关键字开始。如:
class A{
friend void who(const string&);
private:
string a[5];//who()可访问
map<string,string>b;//同上
public:
//...
};
如果一整个类作为友元,则可定义为友元类。如:
class A{
public:
void f();
void g();
};
class B{
friend class A;
//...
};
10.较大的类的拆分——派生类
太大的类,不仅十分繁杂,且有点面向过程的感觉。因此,建议大家拆分成基类(将派生类组合起来)和派生类(实现)或全局函数。
下面是一个例子:
class count_money{
protected:
map<string,float> name_price;
float all(const map<string,float>);
bool enough(const float&);
private:
int time;
int isbn(){ return time++; }//购买编号
double minus_price(float&price1){ return 0.8*price1; } //打折
float much_minus(float&price){ return price-200; }
public:
double total;
float counts();
void present();
counts()=default;
~counts()=default;
};
这个类较复杂,可以拆分:
class counts_money{
protected:
map<string,float> name_price;
float all(const map<string,float>);
private:
int time;
public:
int total;
int isbn(){ return time++; }
};
class minus:public counts_money{
private:
bool enough(const float&);
public:
double minus_price(float&price1){ return 0.8*price1; } //打折
float much_minus(float&price){ return price-200; }
float counts();
};
void present();
每个类都较简洁。
11. 作用域与隐藏名字
作用域需用::来访问,或用using指示以省略::来访问。
类也有一个作用域,所以,尽管两个类的成员完全相同,其成员认识有差别的。如:
class same{
public:
default_random_engine same;
void get_number();
};
class same_too{
public:
default_random_engine same;
void get_number();
};
我们看到,两边的成员是完全一样的,但是,他们处在两个不同的作用域中,所以是有差别的。
隐藏
由于是两个作用域,所以互相之间隐藏了其它作用域的名字。
12.局部作用域与静态成员
一般来说,局部作用域一般指函数的作用域,离开作用域之后所有非静态成员都将会执行析构函数。但是,静态成员除外。
当我们需要保存上一次函数的运行数据是,局部静态成员就非常有用了。
如以下代码:
class example{
public:
int count_calls_times()
{
static int times;
return timrs++;//因为是局部静态成员,所以times并不会被析构
}
};
局部作用域不可用“::”访问
由于局部作用域的成员会被销毁(静态成员除外),所以我们不能用::来访问。
13.初识嵌套类及嵌套类作用域
嵌套类是指在类内部,成员函数之外定义的类。但是,嵌套类也是一个独立的类,因此,它也有可见性。外部类可以用访问说明符来操纵嵌套类的可见性。嵌套类在外部类中可以访问,出了外部类便需要::来访问。如:
class A{
public:
class B{
};
};
B是外界可以用::来访问的。
class A{
public:
class B{
};
B* pointer;//是合法的
};
但是如果为private或protected,类的用户便不可访问了。
同第1章所讲,外部类的成员也会被隐藏
14.进入类的作用域
当我们在类内声明了一个成员函数,要在类外定义时,就需要进入类的作用域。进入作用域以::开始。
class A{
public:
ostream& print(ostream&sto,const string&ost);
};
ostream& A::print(ostream&sto,const string&ost)//有A::,我们进入A的作用域了
{
sto<<ost;
return sto;
}
只要加了类名::,就进入了类的作用域
覆盖警告:
你有可能会在类内定义一个在其它作用域中已经出现过的变量。这是,你便要小心二义性错误了。如如下代码是错误的:
class error{
public:
int a;
};
class error_too{
public:
int a;
};
int main()
{
error object;
object.a;
//哪个a?
//...
return 0;
}
上述代码是错误的,因为它没有说明是哪个a。应如何改呢?答案如下:
int main()
{
error object;
object.error::a;
//或error_too::a;
//...
return 0;
}
小心哦!!!
无需担心与全局作用域混淆
因为全局作用域中的变量不需要用.
来访问,因此无需担心会混淆。
14.构造函数(三)
构造函数在用一个成员来初始化另一个成员的时候,一定要注意顺序。初始化顺序要跟成员的声明顺序一致。如以下代码是未定义的。
class A{
private:
int i;
int j;
public:
A(int k):j(k),i(j){ }
};
初识化顺序与成员声明顺序相同。因此这段代码的意思是:用未初始化的j来初始化i!
我们应该这样改:
A(int k):i(k),j(i){ }
14.1默认构造函数
当一个类有多个构造函数时,默认构造函数时不可少的。请注意!
class A{
public:
A()=default;//如有其它构造函数,此函数必须!
//...
};
15.重载运算符
重载运算符不建议经常使用,但是必要时还是可以的。
重载运算符不可以创造运算符
15.1.重载运算符与this指针
在类的内部,有一个名为this的隐式指针。它指向获取成员的对象。因此,我们在类内重载运算符的时候,要特别注意this指针是一个隐式指针。
class A{
public:
operator+(A&);
//隐式为(A&,*this)
//...
};
15.2.重载运算符与成员函数
有些运算符必须为成员函数。如:[]
等。还有几个运算符不可重载:. .* ?:
16.编译器处理类在int main()中的成员调用与获取
16.1.this指针
这一切与this指针还是脱不了干系。实际上,编译器在处理成员函数的时候,会自动的加上一个实参:const *this
。因此,有如下代码:
class A{
public:
ostream& print(const string=" ");
};
int main()
{
A a;
a.print();
};
编译器将翻译为:
ostream& print(const string=" ");
//伪代码
print(&a);
此时*this=&a;
17.编写辅助的类
17.1.概述
有时,由于有继承关系,我们常常会要将基类和派生类放在同一个集合中(容器),但是,由于基类不能转化为派生类类型,所以我们不能再容器内放入派生类类型的对象。如果我们放入基类对象,虽然也可以继续放入派生类对象,但是,他们再也不是派生类对象了(详见18章)。
因此,我们常常会编写一些辅助的类来处理此情况。
参考以下代码(使用第10章代码):
class counts_money{
protected:
map<string,float> name_price;
float all(const map<string,float>);
private:
int time;
public:
int total;
int isbn(){ return time++; }
};
class minus:public counts_money{
private:
bool enough(const float&);
public:
double minus_price(float&price1){ return 0.8*price1; } //打折
float much_minus(float&price){ return price-200; }
float counts();
};
void present();
现在,我们添加一个用户类:
class account:public counts_money{
private:
double all_price=0.0;
void add(const string&,const double&);
void minus(const string&,const double&);
public:
map<string,double> buys;
void control();
};
那么问题来了:如果现在定义一个储存购买物品的容器,该放什么类型的对象呢?
根据17.1,我们不应该放任何对象,那么,我们最好写一个类。
关于更加深的关于类的知识,详见第七章:C++类(七)《一谈类的更深与番外》(预计2020四月初,三月的写成)
如有错误,请在评论中提出。
感谢!!!