类——前面(综合)(六)

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四月初,三月的写成)

如有错误,请在评论中提出。
感谢!!!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值