Part 4
一 this指针
有一个特殊的指针,它叫做this。我们从来没有见过他,但是他却从来都存在。我们通过一个典型的例子来认识它:
class Human {
char fishc;
Human(char fishc);
}
Human::Human(char fishc){
fishc = fishc;
}
我们看到,在”fishc = fishc”之前,所有的语法都没有任何问题:
Human()构造器有一个名为fishc的参数
虽然他与Human类里边的属性同名,但却是不相干的两样东西,所以并没有错。
可是,问题是怎样才能让构造器知道哪个是参数,哪个是属性呢?
这时候,就需要用到他了 – this指针。
this -> fishc = fishc
现在编译器就懂了,赋值操作符的左边将被解释为当前对象的fishc属性,右边将被解释为构造器的传入来的fishc参数。
注意:
使用this指针的基本原则是:如果代码不存在二义性隐患,就不必使用this指针!
this指针在一些更加高级的方法里也会用到,但我们暂时不想把事情弄得那么复杂。。。。。
二 类的继承
继承是面对对象编程技术的一个核心概念,它使传统的软件开发模式发生了革命性的变化。
继承机制使得程序员可以创建一个类的堆叠层次结构,每个子类均将继承在它的基类里定义的方法和属性。
那么到底有啥作用?
简单地说,通过继承机制,程序员可以对现有的代码进行进一步的扩展,并应用在新的程序中。
那么我们就试着编写一个Animal类作为Turtle类和Pig类的基类。
基类:
基类是可以派生出其他的类,也称为父类或超类。比如这里的Animal类是基类。
子类:
子类是从基类派生出来的类,比如这里的Turtle类和Pig类是子类。
那么Animal类就拥有了Turtle类和Pig类的共同特征:吃东西、睡觉、流口水。
这里我们把这些动作都抽象化为方法
eat(), sleep(), drool();
代表吃东西、睡觉、流口水的eat(), sleep(), drool() 是Animal类里的方法,因为每只动物都会做这些动作。
而swim() 方法在Turtle类里实现,climb() 方法在Pig类里实现。
动物都有嘴巴,而嘴巴是名词不是动作,所以要翻译成类的属性,而不能翻译成类的方法。
我们将mouth转变为Animal类的一个成员变量(属性)。
语法
class SubClass : public SuperClass { … }
class Pig : public Animal { … }
Eg
#include <iostream>
#include <string>
class BaseClass
{
public:
BaseClass();
~BaseClass();
void doSomething();
};
class SubClass : public BaseClass
{
public:
SubClass();
~SubClass();
};
BaseClass::BaseClass()
{
std::cout << "进入基类构造器。。。。。\n";
std::cout << "我在基类构造器里边干了某些事。。。。\n\n";
}
BaseClass::~BaseClass()
{
std::cout << "进入基类析构器.......\n";
std::cout << "我在基类析构器里边也干了某些事。。。。\n\n";
}
void BaseClass::doSomething()
{
std::cout << "我干了某些事。。。。\n\n";
}
SubClass::SubClass()
{
std::cout << "进入子类构造器.....\n";
std::cout << "我在子类构造器里边还干了某些事.....\n\n";
}
SubClass::~SubClass()
{
std::cout << "进入子类析构器......\n";
}
int main()
{
SubClass subclass;
subclass.doSomething();
std::cout << "完事,收工!\n";
return 0;
}
关于构造器的设计要越简明越好!我们应该只用它来初始化各种有关的属性。
作为一个基本原则,在设计、定义和使用一个类的时候,应该让它的每个组成部分简单到不能再简单!
最后一点别忘了,析构器的基本用途是对前面所做的事情进行清理。尤其是在使用了动态内存的程序里,析构器将至关重要!
三 访问控制
在此前的例子里,我们无论是Animal, Pig 和 Turtle 类的所有成员都是用 public: 语句声明。
所谓访问控制,就是C++ 提供了一种用来保护类里的方法和属性的手段。
这里所说的保护意思是对谁可以调用某个方法和访问某个属性加上一个限制。如果某个对象试图调用一个它无权访问的函数,编译器将报错。
我们看下C++中的访问级别:
级别 | 允许谁来访问 |
public | 任何代码 |
protected | 这个类本身和它的子类 |
private | 只有这个类本身 |
利用访问级别来保护类里的方法和属性很简单,只要在类里的某个地方写出一个访问级别并在其后加上一个冒号,从那个地方开始往后的所有方法和属性都将受到相应的保护,直到遇到下一个访问级别或者到达这个类的末尾为止!
class Animal
{
public:
std::string name;
Animal(std::string theName);
void eat();
void sleep();
void drool();
};
请看 name 属性的访问级别是 public,这就意味着任何代码都可以改变它的值。事实我们今后就完全通过 pig.name = “小甲鱼” 来任意改名字了。
我们发觉,如没有任何限制,猪的名字一下子就可以改掉了。这种隐患对这个简单的小程序来说可能没什么大不了的,但如果是发生在一个大型的程序里就足以引发一个逻辑漏洞。
注:BUG无法避免的原因正是因为我们无法模拟各种情况的的输入和修改带来的影响。
就像我们不能随便改变一个人的身份证的名字一样,Animal 类里的 name 属性应该受到保护。
尝试一下:test2
一定要记住使用这些访问级别!即时只有你一个人在开发某个项目,全部记住各个类的调用方法也是一件困难的事情。
给每个方法和属性加上 protected 或 private 访问级别,就由编译器替你记住那些禁令并在你违反的时候发出警报。
使用访问级别对身为程序员的你只有好处,没有坏处!再优秀的程序员也需要这种机械的保护!
使用 private 的好处是,今后可以只修改某个类的内部实现,而不必重新修改整个程序。这是因为其他代码根本就访问不到 private 保护的内容,所以不怕”牵一发而动全身”的惨剧发生!
在同一个类定义里可以使用多个 public:, private: 和 protected: 语句,但最好把你的元素集中到一个地方,这样代码的可读性会好很多。
在编写你的类定义代码时,应该从 public: 开始写起,然后是 protected:, 最后是 private:。
虽然编译器并不挑剔这些顺序,但这么做的好处是 —— 当你想知道某个特定的类提供了哪些方法和属性时,好的顺序可以为你节省大量的时间!
课后想一想:
class Pig : public Animal { … } 是什么意思?!
从基类继承来的方法和属性的保护:
class Pig : public Animal { … }
C++ 不仅允许你对在类里定义的方法和属性实施访问控制,还允许你控制子类可以访问基类里的哪些方法和属性。
public
是在告诉编译器:继承的方法和属性的访问级别不发生任何改变 – 即 public 仍可以被所有代码访问,protected 只能由基类的子类访问,privat 则只能由基类本身访问。
protected
把基类的访问级别改为 protected , 如果原来是 public 的话。这将使得这个子类外部的代码无法通过子类去访问基类中的 public 。
private
是在告诉编译器从基类继承来的每一个成员都当成 private 来对待,这意味着只有这个子类可以使用它从基类继承来的元素。
二 类的方法覆盖以及重载
通过之前的学习,我们已经知道了如何通过创建新的子类来重用现有的代码(继承)。
虽然这个方案可以让我们轻松解决许多现实世界里的问题,但在某些场合,却又显得不够用。
例如当我们需要在基类里提供一个通用的函数,但在它的某个子类里需要修改这个方法的实现,在 C++ 里,覆盖(overriding)就可以做到。
回到我们之前的例子,我们都知道,但凡是个动物都知道吃!那么吃我们就可以说是动物的一个共同特征,但我们知道不同动物会有不同的吃法。。。。。。
C++ 可以让我们很容易实现这种既有共同特征又需要在不同的类里有不同实现的方法。
我们需要做的是在类里重新声明这个方法,然后再改写一下它的实现代码(就像它是一个增加的方法那样)就行啦。
原版本
#include <iostream>
#include <string>
class Animal
{
public:
Animal(std::string theName);
void eat();
void eat(int eatCount);
void sleep();
void drool();
protected:
std::string name;
};
class Pig : public Animal
{
public:
Pig(std::string theName);
void climb();
};
class Turtle : public Animal
{
public:
Turtle(std::string theName);
void swim();
};
Animal::Animal(std::string theName)
{
name = theName;
}
void Animal::eat()
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::eat(int eatCount)
{
std::cout << "我吃了" << eatCount << "碗馄饨!\n\n";
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!\n" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的我会流口水,我正在流口水。。。\n" << std::endl;
}
Pig::Pig(std::string theName) : Animal(theName)
{
}
void Pig::climb()
{
std::cout << "我是一个只漂亮的小母猪猪,我会上树,我正在爬树,嘘。。。\n" << std::endl;
}
Turtle::Turtle(std::string theName) : Animal(theName)
{
}
void Turtle::swim()
{
std::cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里。。哈哈。。\n" << std::endl;
}
int main()
{
Pig pig("小猪猪");
Turtle turtle("小甲鱼");
// std::cout << "这只猪的名字是: " << pig.name << std::endl; // 错误
// std::cout << "每只乌龟都有个伟大的名字: " << turtle.name << std::endl; // 错误
pig.eat();
turtle.eat();
pig.eat(15);
pig.climb();
turtle.swim();
return 0;
}
1.覆盖
#include <iostream>
#include <string>
class Animal
{
public:
Animal(std::string theName);
void eat();
void sleep();
void drool();
protected:
std::string name;
};
class Pig : public Animal
{
public:
Pig(std::string theName);
void climb();
void eat(); // new!
};
class Turtle : public Animal
{
public:
Turtle(std::string theName);
void swim();
void eat(); // new!
};
Animal::Animal(std::string theName)
{
name = theName;
}
void Animal::eat()
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!\n" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的我会流口水,我正在流口水。。。\n" << std::endl;
}
Pig::Pig(std::string theName) : Animal(theName)
{
}
void Pig::climb()
{
std::cout << "我是一个只漂亮的小母猪猪,我会上树,我正在爬树,嘘。。。\n" << std::endl;
}
void Pig::eat()
{
Animal::eat();
std::cout << name << "正在吃鱼!\n\n"; // new!
}
Turtle::Turtle(std::string theName) : Animal(theName)
{
}
void Turtle::swim()
{
std::cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里。。哈哈。。\n" << std::endl;
}
void Turtle::eat()
{
Animal::eat();
std::cout << name << "正在吃东坡肉!\n\n"; // new!
}
int main()
{
Pig pig("小猪猪");
Turtle turtle("小甲鱼");
// std::cout << "这只猪的名字是: " << pig.name << std::endl; // 错误
// std::cout << "每只乌龟都有个伟大的名字: " << turtle.name << std::endl; // 错误
pig.eat();
turtle.eat();
pig.climb();
turtle.swim();
return 0;
}
- 重载
#include <iostream>
#include <string>
class Animal
{
public:
Animal(std::string theName);
void eat();
void eat(int eatCount);
void sleep();
void drool();
protected:
std::string name;
};
class Pig : public Animal
{
public:
Pig(std::string theName);
void climb();
};
class Turtle : public Animal
{
public:
Turtle(std::string theName);
void swim();
};
Animal::Animal(std::string theName)
{
name = theName;
}
void Animal::eat()
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::eat(int eatCount)
{
std::cout << "我吃了" << eatCount << "碗馄饨!\n\n";
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!\n" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的我会流口水,我正在流口水。。。\n" << std::endl;
}
Pig::Pig(std::string theName) : Animal(theName)
{
}
void Pig::climb()
{
std::cout << "我是一个只漂亮的小母猪猪,我会上树,我正在爬树,嘘。。。\n" << std::endl;
}
Turtle::Turtle(std::string theName) : Animal(theName)
{
}
void Turtle::swim()
{
std::cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里。。哈哈。。\n" << std::endl;
}
int main()
{
Pig pig("小猪猪");
Turtle turtle("小甲鱼");
// std::cout << "这只猪的名字是: " << pig.name << std::endl; // 错误
// std::cout << "每只乌龟都有个伟大的名字: " << turtle.name << std::endl; // 错误
pig.eat();
turtle.eat();
pig.eat(15);
pig.climb();
turtle.swim();
return 0;
}
对方法(函数)进行重载一定要有的放矢,重载的方法(函数)越多,程序就越不容易看懂。
在对方法进行覆盖(注意区分覆盖和重载)时一定要看仔细,因为只要声明的输入参数和返回值与原来的不一致,你编写出来的就将是一个重载方法而不是覆盖方法。这种错误以小甲鱼的个人经验告诉你:往往很难调试!
对从基类继承来的方法进行重载,程序永远不会像你预期的那样工作!(try)
#include <iostream>
#include <string>
class Animal
{
public:
Animal(std::string theName);
void eat();
void sleep();
void drool();
protected:
std::string name;
};
class Pig : public Animal
{
public:
Pig(std::string theName);
void climb();
void eat(int eatCount);
};
class Turtle : public Animal
{
public:
Turtle(std::string theName);
void swim();
};
Animal::Animal(std::string theName)
{
name = theName;
}
void Animal::eat()
{
std::cout << "I'm eatting!" << std::endl;
}
void Animal::sleep()
{
std::cout << "I'm sleeping!Don't disturb me!\n" << std::endl;
}
void Animal::drool()
{
std::cout << "我是公的,看到母的我会流口水,我正在流口水。。。\n" << std::endl;
}
Pig::Pig(std::string theName) : Animal(theName)
{
}
void Pig::climb()
{
std::cout << "我是一个只漂亮的小母猪猪,我会上树,我正在爬树,嘘。。。\n" << std::endl;
}
void Pig::eat(int eatCount)
{
std::cout << "我吃了" << eatCount << "碗馄饨!\n\n";
}
Turtle::Turtle(std::string theName) : Animal(theName)
{
}
void Turtle::swim()
{
std::cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里。。哈哈。。\n" << std::endl;
}
int main()
{
Pig pig("小猪猪");
Turtle turtle("小甲鱼");
// std::cout << "这只猪的名字是: " << pig.name << std::endl; // 错误
// std::cout << "每只乌龟都有个伟大的名字: " << turtle.name << std::endl; // 错误
pig.eat();
turtle.eat();
pig.eat(15);
pig.climb();
turtle.swim();
return 0;
}
也就是说继承之后不能够进行重载了,只能够在同一个类之中进行重载,崽子类之中那叫做覆盖而不能叫重载