(C++学习系列)第四集.

1.传值,传址,传引用

1.1传值

首先需要明白的是传值的概念,它指的是在函数中直接把值进行传递,而不是以地址进行传递。代码如下

#include <iostream>
using namespace std;
 
void change(int age1, int age2);
 
int main()
{
	int age = 24;
 
	change(age, age + 1);//值传递,不是址传递,输出为25
	cout << "age = " << age << endl;//输出为24
 
	return 0;
}
 
void change(int age1, int age2)//值传递,不是址传递
{
	age1 = age2;/*函数内部修改了值不会影响外部,除非是对地址的改变*/
	cout << "age1 = " << age1 << endl;
}

输出为:

这里可以发现age把自己的值直接传进之后,在函数里改变了值,却无法传递到函数的外部。

1.2传址

同样的代码,通过指针进行存储地址变量传递到函数中,记得地址是唯一的地址,通过解引用来访问并改变的值,即那个地址上的值进行了改变,所以是可以影响全局的。代码如下:

/*
函数内传值进行值改变无法影响函数外部,所以需要用指针来存址。
传地址需要用*对指针进行解引用
*/
#include <iostream>
using namespace std;
 
void change(int *age1, int age2);
 
int main()
{
	int age = 24;
 
	change(&age, age + 1);//第一个参数传输地址过去,址传递,输出为25
	cout << "age = " << age << endl;//输出为25
 
	return 0;
}
 
void change(int *age1, int age2)//址传递
{
	*age1 = age2;/*改变了age1的地址,age1的值通过地址被改变,因为地址是唯一的,一定是改变的*/
	cout << "age1 = " << *age1 << endl;
}

地址上对应的值改变了。那必然会影响这个变量的值。因此结果如下:

下面一个经典的代码,交换两个数的大小,同样用到指针来进行储存地址,可以在把两者的交换传递到函数的外面。

#include <iostream>
using namespace std;

void swap(int *a , int *b);/*C++里一定要有函数声明!*/

int main()
{
    int a = 5;
    int b = 6;

    cout << "原来" << "a = " << a << '\n' << "b = " << b <<endl;

    swap(&a, &b);

    cout << "交换后" << "a = " << a << '\n' << "b = " << b <<endl;
    return 0;
}


void swap(int *a , int *b)/*void型不应该有返回值*/
{
    *a = *a ^ *b;/*三次异或可以实现不需要第三个值两者交换*/
    *b = *a ^ *b;/*因为一个数异或同一个数两次,就等于原本的数。这里a^b^b就等于a赋值给b,同理*/
    *a = *a ^ *b;/*也可以这样理解,这里a^b以后变成了中间值,然后中间值^其他值就变成其他值了*/
}

这里采用了异或运算,就不需要第三个数来做媒介让两个数交换了

1.3传引用

这个主要是在声明和定义函数的时候,直接声明成地址进行传递(仅限于C++)

/*引用传递*/
/*
C++完善了地址这一个概念,如果事先就知道某个函数的参数只能接受一个地址,
能不能使用某种约定使得在调用该函数的时候不需要指针语法呢
其实他和传址的目的是一样的,都是把地址传递给函数,但语法不同更加容易使用了
*/

/*这里以swap改造*/
#include <iostream>
using namespace std;
/*函数声明中直接表明是整形地址,传递的时候直接传,就不需要传&a了*/
void swap(int &a , int &b);/*C++里一定要有函数声明!*/

int main()
{
    int a = 5;
    int b = 6;

    cout << "原来" << "a = " << a << '\n' << "b = " << b <<endl;

    swap(a, b);/*直接传a和b*/

    cout << "交换后" << "a = " << a << '\n' << "b = " << b <<endl;
    return 0;
}


void swap(int &a , int &b)/*这里直接用传入的a和b 运算,不是指针所以不需要解引用,输入a和b自动识别的是他们的地址*/
{
    a = a ^ b;/*三次异或可以实现不需要第三个值两者交换*/
    b = a ^ b;/*因为一个数异或同一个数两次,就等于原本的数。这里a^b^b就等于a赋值给b,同理*/
    a = a ^ b;/*也可以这样理解,这里a^b以后变成了中间值,然后中间值^其他值就变成其他值了*/
}

直接在函数内部就改变了地址,这里不需要用指针,在函数里会自动对其地址对应的值进行改变

输出同上

2.联合,枚举和类型别名

2.1联合

联合就是union,联合和结构有很多相似之处,联合也可以容纳多种不同类型的值,但是它每次只能存储这些值中的某一个。

例如:需要定义一个变量来存放密码,密码只有一个

union mima
{
    unsigned long birthday;
    unsigned short ssn;
    char* pet;
}
mima_1.birthday = 19881301
mima_1.pet = "chaozai" 如果定义了这个,会把原来的birthaday成员的值直接抛弃

举例:

#include <iostream>
using namespace std;
 
union T
{
	const char *name;
	int a;
	char ch;
};
 
int main()
{
	T birthday;
	birthday.name = "WaHaHa";
	cout << "birthday.name = " << birthday.name << endl;
 
	birthday.a = 3;//此时birthday.name中的内容被覆盖
	cout << "birthday.a = " << birthday.a << endl;
 
	return 0;
}

2.2枚举

枚举主要是为了创建一个可取值列表(会自动从0赋值),代码如下:

#include <iostream>
using namespace std;
 
int main()
{
	//枚举中的值不是字符串,所以不用引号
	enum weekends{ Monday, Tuesday, Wednesday, Thursday, Friday};
 /*用0到n-1的整数关联起来,有点像集中定义的宏*/
	weekends today;
	today = Monday;
	cout << "today = " << today << endl;//输出0
	today = Wednesday;
	cout << "today = " << today << endl;//输出2
 
	return 0;
}

这里默认Monday为0,后面依次增加1,输出为:

2.3类型别名

typedef是用来定义类型别名的,比如typedef int* intPointer,那之后就可以用intPointer代替int*

3.介绍对象!

3.1创建一个类

在C++中,对象的本质就是新的数据类型,拥有无限的潜力。

类就是一个对象,我们从类开始,首先类是一个模型,当我们从这个类进行创建实例的时候,也就是对象本身,拥有扩展型和前瞻性。

类是一幅蓝图,决定对象是什么样的(具备什么属性和功能)
OOP(Object Oriented Programming面向对象编程)过程的第一步就是创建一个类,每个类都有一个名字:

class MyFirstClass
{
};

类是由变量(属性)和函数组成,对象将使用那些变量来存储信息,调用那些函数来完成操作。
所以人们常常会看到一些专门术语:类里面的变量成为属性,函数成为方法。当然本质没有改变。接下来我们来创建一个类(一个车)

#include <iostream>
using namespace std;
const unsigned int FULL_GAS = 80;
/*
声明了一辆车的简单属性
我们应该为类定义一些方法,其实也就是定义一些函数。创建个人函数也是两个步骤:
首先是创建函数的声明,再描述函数本身的实现过程
*/
class Car
{
    public:
    string color;
    string engine;
    float gas_tank;/*存放的油量*/
    unsigned int Wheel;

/*现在我们的Car类有了一个名为fillTank的方法,只有一个输入参数,没有返回值*/
    void fill_tank(float liter);/*加油函数*/
    void setEngine(string end); /*设置引擎*/
    void setColor(string col); /*给车上色*/
	void setWheel(unsigned int whe);/*设置轮子*/
    void warning();/*警报*/
    int running(void);/*运行函数*/
};
/*::是作用域解析操作符,作用是告诉编译器这个方法存于何处*/

void Car::warning()
{
	cout << "还剩油量:" << 100 * gas_tank / FULL_GAS << '%' << endl;
}
void Car::setEngine(string eng)
{
	engine = eng;
}
void Car::setWheel(unsigned int whe)
{
	Wheel = whe;
}
void Car::setColor(string col)
{
	color = col;
}
void Car::fill_tank(float liter)
{
	gas_tank += liter;
}
int Car::running()
{
	cout << "小车正在以120的速度跑着!!!" << endl;
	gas_tank--;
	cout << "当前油量:" << 100 * gas_tank / FULL_GAS << '%' << endl;
	return gas_tank;/*返回当前油量*/
}


int main()
{

	char ch;//存储加油命令
	Car myCar;
	myCar.setColor("Black");
	myCar.setWheel(4);
	myCar.setEngine("V8");
 
	myCar.gas_tank = FULL_GAS;//加满油
	while ( myCar.running() )
	{
		if (myCar.running() < 10)
		{
			myCar.warning();
			cout << "是否需要加油【Y/N】:" << endl;
			cin >> ch;
			switch (ch)
			{
			case 'Y':
			case 'y':
				myCar.fill_tank(FULL_GAS);
				break;
			case 'N':
			case 'n':
				cout << "不加就不加" << endl;
			default:
				break;
			}
		}
 
	}
 
 
	return 0;

}
/*面向對象編程技術,可以說是面向過程技術的替代平。他可以有自己的函數。
不允许类里面声明常量!但可以用静态常量,static const float FULL_GAS = 85;但一般没必要
类的话就不需要typedef来重命名了,因为不需要加class

这里我们在主函数里进行初始化(目前还没有用到构造器来初始化,后文会详细介绍)

3.2构造器和析构器

3.2.1构造器

构造器的主要功能是初始化函数,再用类创建实例的时候,第一个掉的就是构造器

构造器和通常方法的主要区别:
- 构造器的名字必须和它所在的类的名字一样
- 系统在创建某个类的实例的时候会第一时间自动调用这个类的构造器
- 构造器永远不会返回任何值!!!

创建构造器,需要把它的声明添加到类里,类名开头大写!

class Car
{
public:
    Car(void);
    ………………
}

Car::Car(void)    //不需要最前面加void,因为肯定不会有任何返回值
{
    color = "White";
    engine = "V8";
    wheel = 4;
    gas_tank = FULL_GAS;
}

在类的内部可以直接调用这些属性值,不需要用实例.   来进行取

代码案例如下:

#include <iostream>
using namespace std;
const unsigned int FULL_GAS = 80;

class Car
{
    public:
    string color;
    string engine;
    float gas_tank;/*存放的油量*/
    unsigned int Wheel;

    Car(void);/*构造器*/

/*现在我们的Car类有了一个名为fillTank的方法,只有一个输入参数,没有返回值*/
    void fill_tank(float liter);/*加油函数*/
    void setEngine(string end); /*设置引擎*/
    void setColor(string col); /*给车上色*/
	void setWheel(unsigned int whe);/*设置轮子*/
    void warning();/*警报*/
    int running(void);/*运行函数*/
};
/*::是作用域解析操作符,作用是告诉编译器这个方法存于何处*/
Car::Car(void)/*构造函数用来初始化*/
{
    color = "White";
    engine = "V8";
    Wheel = 4;
    gas_tank = FULL_GAS;
}

void Car::warning()
{
	cout << "还剩油量:" << 100 * gas_tank / FULL_GAS << '%' << endl;
}
void Car::setEngine(string eng)
{
	engine = eng;
}
void Car::setWheel(unsigned int whe)
{
	Wheel = whe;
}
void Car::setColor(string col)
{
	color = col;
}
void Car::fill_tank(float liter)
{
	gas_tank += liter;
}
int Car::running()
{
    char ch;
	cout << "小车正在以120的速度跑着!!!" << endl;
	gas_tank--;
	cout << "当前油量:" << 100 * gas_tank / FULL_GAS << '%' << endl;
    if (gas_tank < 10)
	{
		warning();
		cout << "是否需要加油【Y/N】:" << endl;
		cin >> ch;
		switch (ch)
		{
		case 'Y':
		case 'y':
			fill_tank(FULL_GAS);
			break;
		case 'N':
		case 'n':
			cout << "不加就不加" << endl;
            break;
		default:
			break;
			}
		}
        if(0 == gas_tank)
        {
            cout << "没有油咯!" << endl;
            return 1;
        }
        return 0;
 
}


int main()
{
	Car myCar;

 
	myCar.gas_tank = FULL_GAS;//加满油
	while (!myCar.running() )
	{
        ;
	}
 
 
	return 0;

}

每个类至少有一个构造器!如果你没有在类里定义一个构造器,编译器就会替你创建一个副本构造器。

ps:

/*可以通过类来构造实例数组*/
Car mycar[10];
mycar[x].running;/*之类的*/

3.2.2析构器

在创建对象的时候,系统都会自动调用一个特殊的方法,即构造器。相应地,在销毁一个对象的时候,系统也应该会调用另一个特殊的方法来达到对应效果,即析构器。

一般来说,构造器用来完成事先的初始化和准备工作(申请分配内存),析构器用来完成事后所必须清理的工作(清理内存),析构器也不返回任何参数。

析构器也是跟类同名,但是前面需要加一个~

代码案例如下,功能是写入名言到文本中,这里析构器用来处理关闭文件

#include <iostream>
using namespace std;

#include <string>//字符串操作
#include <fstream>//文件操作
 
class StoreQuote
{
public:
	StoreQuote();//构造器
	~StoreQuote();//析构器
 
	string quote, speaker;//存储名言和作者
	ofstream OutFile;//写入文件类
 
	void inputQuote();
	void inputSpeaker();
	bool write();
	bool ifwrite();
 
private:
};
 

//构造器:事先完成初始化和准备工作
StoreQuote::StoreQuote()
{
	OutFile.open("test.txt", ios::app);//追加的方式写入文件
}

//析构器:完成时候的清理工作
StoreQuote::~StoreQuote()
{
	OutFile.close();//关闭文件
}
 
void StoreQuote::inputQuote()
{
	getline(cin, quote);//getline是string中的类,从输入中读取到quote中
}
 
void StoreQuote::inputSpeaker()
{
	getline(cin, speaker);
}
bool StoreQuote::write()
{
	//判断文件是否打开
	if (OutFile.is_open())/*先写入字符串这里写入文件*/
	{
		OutFile << quote << "||来自“" << speaker <<  "”" << endl;/*换行了*/
		return true;
	}
	else
	{
		return false;
	}
}
bool StoreQuote::ifwrite()
{
	char ch;
	cout << "是否写入格言【Y/N】:";
	cin >> ch;
	cin.ignore(100, '\n');//忽略100个字符,除非遇到回车,这是为了清空输入流,即回车后把之前输入流的清空,这样才能接收新的输入
	switch (ch)
	{
	case 'Y':
	case 'y':
		cout << "将格言写入文件" << endl;
		return true;
		break;
	case 'N':
	case 'n':
		cout << "格言不写入文件" << endl;
		return false;
		break;
	default:
		return false;/*break是一个好习惯,跳出switch,虽然这里有return了,但是还是加一下break把*/
		break;
	}
}
 
int main()
{
	StoreQuote quote;
 
	while ( quote.ifwrite() )
	{
		cout << "请输入一句格言:" << endl;
		quote.inputQuote();/*如果没有ignore的话,第一个Y以及回车会被读入cin中,直接触发getline了,而不等你输出*/
		cout << "请输入作者的姓名:" << endl;
		quote.inputSpeaker();
 
		if (quote.write())
		{
			cout << "数据写入成功!" << endl;
		}
		else
		{
			cout << "数据写入失败!" << endl;
            cout << "输入任意字符退出" << endl;
            cin.get();
            return 1;
		}
	}
    cout << "输入任意字符退出" << endl;
    cin.get();
	return 0;
}

3.3this指针(暂时用不到)

当我们定义一个类的时候

class Human
{
    char fishc;
    Human(char fishc);   同名构造函数!
};

构造器里有一个名为fishc的参数,虽然他和Human类里面的属性同名,但是却不相干
所以需要让构造器明白到底谁才是参数,谁才是类的属性,这时候就需要用到this指针
this->fishc = fishc   左边是类的fishc属性,右边是构造器传入来的参数
存在二异性才用到this指针

3.4类的继承(重要)

基本思想:程序员可以对现有的代码进行进一步的扩展,并应用在新的程序中,比如写好了对象A,然后需要B去实现更复杂功能,且有A的特征,可以用B类去继承A。

假设有一只乌龟和一只猪,他们有共同特征:吃东西,睡觉,流口水,也有不同地方:乌龟会游泳,猪会爬树。两者都是动物,他们都是动物的子类

class SubClass : public SuperClass{…}  /*继承的写法*/
class Pig : public Animal{…}

 代码如下:

#include <iostream>
using namespace std;

class Animal
{
public:
    string mouth;

    void eat();
    void sleep();
    void drool();
};

class Pig : public Animal
{
public:
    void climb();
};

class Turtle : public Animal
{
public:
    void swim();
};

/*注意函数的写法哦*/
void Animal::eat()
{
    cout << "我正在吃" << endl;
}

void Animal::sleep()
{
    cout << "我正在睡觉" << endl;
}

void Animal::drool()
{
    cout << "我正在打呼噜" << endl;
}

void Pig::climb()
{
    cout << "我正在上树" << endl;
}

void Turtle::swim()
{
    cout << "我正在游泳" << endl;
}

int main()
{
    /*初始化对象*/
    Pig pig;
    Turtle turtle;
    pig.eat();
    turtle.swim();
    return 0;
}

 3.5继承机制中的构造器和析构器

如基类有一个构造器,如Animal(),它将在创造Pig类型的对象时最先被调用,如果Pig类也有一个构造器,它将排在第二个被调用,因为基类必须要在子类之前初始化原则。

简易代码:(当构造器中有参数的时候写法),继承的子类的构造器就不许添加内容了,直接继承了基类的,但是赋值给子类

#include <iostream>
using namespace std;

class Animal
{
public:
    Animal(string theName);
    string name;
};

class Pig : public Animal
{
public:
    Pig(string theName);
};

Animal::Animal(string theName)
{
    name = theName;
}
/*这样写是正确的!*/
Pig::Pig(string theName) : Animal(theName)/*这波是直接继承*/
{
}
/*当调用Pig构造器的时候(以theName作为输入参数),Animal()的构造器也将被调用(以theName作为输入参数)*/
/*这个赋值动作实际上是发生在Animal里的,比如调用Pig pig("小猪猪")*/

当在主函数里调用Pig pig进行实例化的时候,这里构造器有参数所以正确写法是 Pig pig("小猪猪"),就会把小猪猪赋值给pig.name

整体代码:

/*继承中的构造器和析构器:基类和子类*/
#include <iostream>
using namespace std;

#include <string>
//基类(父类)动物
class Animal
{
public:
	//属性
	string mouth;
	string name;
 
	Animal(string theName);
	//方法
	void eat();
	void sleep();
	void drool();
};
//子类1猪猪
class Pig : public Animal
{
public:
	void climb();
	Pig(string theName);//构造器
};
//子类2王八
class Turtle : public Animal
{
public:
	void swim();
	Turtle(string theName);
};
Animal::Animal(string theName)
{
	name = theName;
}
//基类中方法定义
void Animal::eat()
{
	cout << "I am eating!!!" << endl;
}
void Animal::sleep()
{
	cout << "I am sleeping!!!" << endl;
}
void Animal::drool()
{
	cout << "I am drooling!!!" << endl;
}
//子类中方法定义
void Pig::climb()
{
	cout << "我是一只漂亮的小母猪,我会上树,我正在爬树!" << endl;
}
Pig::Pig(string theName) : Animal(theName)//子类构造器定义
{
}
void Turtle::swim()
{
	cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里,哈哈!" << endl;
}
 
Turtle::Turtle(string theName):Animal(theName)//子类构造器定义
{
}
 
int main()
{
    /*使用Pig pig(参数)是用来调用构造器的!因为Pig pig初始化的时候就是调用了构造器,这里构造器需要参数,所以给个参数*/
	Pig pig("小猪猪");/*这里就直接传递了the Name给了构造器,是在Aniaml类的构造器里赋值的,但是继承过来了*/
	Turtle turtle("小甲鱼");
 
	cout << "这只猪的名字叫" << pig.name << endl;
	cout << "这只王八的名字叫" << turtle.name << endl;
	//子类自有的方法
	pig.eat();
	turtle.eat();
	//子类继承基类中的方法
	pig.climb();
	turtle.swim();
 

	return 0;
}

 

 

总体的调用顺序如下:

基类构造器—>子类构造器—>子类中的方法—>子类析构器—>基类析构器

为了观察这个顺序,我们可以写一个案例:(这里构造器没有参数,我们可以直接普通写法,不用像上面代码中继承基类的那种写法)

/*基类构造器—>子类构造器—>子类中的方法—>子类析构器—>基类析构器*/
#include <iostream>
using namespace std;


class BaseClass
{
public:
    BaseClass();
    ~BaseClass();
    
    void doSomething();
};

class SubClass : public BaseClass
{
public:
    SubClass();
    ~SubClass();
};

BaseClass::BaseClass()
{
    cout << "进入基类构造器" << '\n';
    cout << "我在基类构造器里干了某些事情" << endl;
}

BaseClass::~BaseClass()
{
    cout << "进入基类析构器" << '\n';
    cout << "我在基类析构器里也干了某些事情" << endl;    
}

void BaseClass::doSomething()
{
    cout << "我干了某些事情" << endl;
}
/*这里不做继承,而是添加自己要干的东西*/
SubClass::SubClass()/*SubClass::SubClass():BaseClass()有参数的时候才这样写,括号里加参数*/
{
    cout << "进入子类构造器" << '\n';
    cout << "我在子类构造器里还干了某些事情" << endl;    
}

SubClass::~SubClass()
{
    cout << "进入子类析构器" << '\n';
    cout << "我在子类析构器里也干了某些事情" << endl;        
}

int main()
{
    SubClass subclass;
    subclass.doSomething();
    cout << "完事,收工" << endl;
    return 0;
}

3.6访问控制

新手需要注意的地方:基类和子类之间的关系应该自然且清晰,关于构造器设计要越简单越好,应该只用它来初始化各种有关的属性。

所谓访问控制,就是C++提供了一种用来保护类里面的方法和属性的手段
各种类允许访问的用户:

  • public:任何代码
  • protected:类本身和它的子类
  • private:只有这个类本身
class Animal
{
public:
    string name;

    Animal(string theName);
    void eat();
    void sleep();
    void drool();
};

因为是public意味着任何代码都可以访问它,所以上述很容易通过pig.name 等来修改名字。

注意这个类本身或者子类可以调用,其他代码不能调用的意思是,只能在类的内部进行运算,比如pig.name就相当于外面的代码进行访问了。如果是protect和private就是不允许的,但是可以构造器进行初始化,如下面案例:

#include <iostream>
using namespace std;

#include <string>
//基类(父类)动物
class Animal
{
public:
	//属性
	string mouth;

 
	Animal(string theName);
	//方法
	void eat();
	void sleep();
	void drool();
protected:
    string name;
};
//子类1猪猪
class Pig : public Animal
{
public:
	void climb();
	Pig(string theName);//构造器
};
//子类2王八
class Turtle : public Animal
{
public:
	void swim();
	Turtle(string theName);
};
Animal::Animal(string theName)
{
	name = theName;
}
//基类中方法定义
void Animal::eat()
{
	cout << "I am eating!!!" << endl;
}
void Animal::sleep()
{
	cout << "I am sleeping!!!" << endl;
}
void Animal::drool()
{
	cout << "I am drooling!!!" << endl;
}
//子类中方法定义
void Pig::climb()
{
	cout << "我是一只漂亮的小母猪,我会上树,我正在爬树!" << endl;
}
Pig::Pig(string theName) : Animal(theName)//子类构造器定义
{
}
void Turtle::swim()
{
	cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里,哈哈!" << endl;
}
 
Turtle::Turtle(string theName):Animal(theName)//子类构造器定义
{
}
 
int main()
{
	Pig pig("小猪猪");/*这是初始化的pig.name,只有这里才能用*/
	Turtle turtle("小甲鱼");
    
    /*如果在这里恶意修改了代码*/
    /*pig.name = "笨蛋";                那么就不叫小猪猪了,叫笨蛋了*/
    /*所以应该在前面写类的时候把name给保护起来*/
        /*这里name就无法访问了*/
	cout << "这只猪的名字叫" << pig.name << endl;
	cout << "这只王八的名字叫" << turtle.name << endl;
	//子类自有的方法
	pig.eat();
	turtle.eat();
	//子类继承基类中的方法
	pig.climb();
	turtle.swim();
 

	return 0;
}

这里构造器初始化可以调用名字(protected),而pig.name就会报错

使用private的好处是,今后可以只修改某个类的内部实现,而不必重新修改整个程序,因为其他代码根本访问不到,不怕牵一发而动全身,把public,protect,private同一个属性的聚集在一块,从public开始写起,然后是protected,最后是private。

3.7覆盖和重载方法

在讲解这两个概念之前,我先提个问题:

想一想类的继承class Pig : public Animal{}是什么意思捏?

答: C++不仅允许你对在类里定义的方法和属性实施访问控制,还允许你对控制子类可以访问基类里的那些方法和属性。
public是在告诉编译器继承的方法和属性的访问级别不发生改变,即public可以被所有代码访问。protected只能由自己和子类访问。
private只能被自己访问。

如:把基类访问级别改为protected,如果原来是public,这将使得这个子类外部的代码,无法通过子类取访问基类中的public!(class Pig : protected Animal{}),private只有这个子类可以使用从基类继承过来的元素。但一般都只用public!(别搞得那么复杂)

3.7.1覆盖

所谓覆盖就是子类继承过来的函数,但是需要有它自己的实现方法,应该和基类有所区别,这个时候就需要用到覆盖。做法就是在类里面重新声明一下这个方法,再改写一下实现代码。

如下:

#include <iostream>
using namespace std;
#include <string>
//基类(父类)动物
class Animal
{
public:
	//属性
	string mouth;
	
	Animal(string theName);
	//方法
	void eat();
	void sleep();
	void drool();
protected:
	string name;//这里定义name为保护的了,只有Animal本身以及其子类才可以修改
};
//子类1猪猪
class Pig : public Animal
{
public:
	void climb();
	void eat();//子类中覆盖基类中的方法
 
	Pig(string theName);//构造器
};
//子类2王八
class Turtle : public Animal
{
public:
	void swim();
	void eat();//子类中覆盖基类中的方法
 
	Turtle(string theName);
};
Animal::Animal(string theName)
{
	name = theName;
}
//基类中方法定义
void Animal::eat()
{
	cout << "I am eating!!!" << endl;
}
void Animal::sleep()
{
	cout << "I am sleeping!!!" << endl;
}
void Animal::drool()
{
	cout << "I am drooling!!!" << endl;
}
//子类中方法定义
void Pig::climb()
{
	cout << "我是一只漂亮的小母猪,我会上树,我正在爬树!" << endl;
}
/*覆盖了animal里面的eat*/
void Pig::eat()
{
	Animal::eat();//直接调用之前的基类函数
	cout << name << "正在吃草" << endl;
}

Pig::Pig(string theName) : Animal(theName)//子类构造器定义
{
}
void Turtle::swim()
{
	cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里,哈哈!" << endl;
}
 /*覆盖了animal里面的eat*/
void Turtle::eat()
{
	Animal::eat();//直接调用之前的基类函数
	cout << name << "我正在吃鱼" << endl;
}
 
Turtle::Turtle(string theName):Animal(theName)//子类构造器定义
{
}
 
int main()
{
	Pig pig("小猪猪");
	Turtle turtle("小甲鱼");
 
	//子类自有的方法
	pig.eat();
	turtle.eat();
	//子类继承基类中的方法
	pig.climb();
	turtle.swim();
 
	system("pause");
	return 0;
}

在子类定义的时候重新声明一次函数,后面还需要重新写一下函数内容(子类::函数名),这样就可以覆盖基类函数的内容。当然只在这个子类中覆盖,其他的子类还是可以调用基类函数的。

3.7.2重载

/*重载方法*/
#include <iostream>
using namespace std;

#include <string>
//基类(父类)动物
class Animal
{
public:
	//属性
    
	string mouth;
	
	Animal(string theName);
	//方法
	void eat();
	void eat(int eatcount);
	void sleep();
	void drool();
protected:
	string name;//这里定义name为保护的了,只有Animal本身以及其子类才可以修改
};
//子类1猪猪
class Pig : public Animal
{
public:
	void climb();
	Pig(string theName);//构造器
};
//子类2王八
class Turtle : public Animal
{
public:
	void swim();
 
	Turtle(string theName);
};
Animal::Animal(string theName)
{
	name = theName;
}
//基类中方法定义
void Animal::eat(int eatcount)
{
	cout << "我吃了" << eatcount << "馄饨" << endl;
}
void Animal::eat()
{
	cout << "I am eating!!!" << endl;
}
void Animal::sleep()
{
	cout << "I am sleeping!!!" << endl;
}
void Animal::drool()
{
	cout << "I am drooling!!!" << endl;
}
//子类中方法定义
void Pig::climb()
{
	cout << "我是一只漂亮的小母猪,我会上树,我正在爬树!" << endl;
}
 
Pig::Pig(string theName) : Animal(theName)//子类构造器定义
{
}
void Turtle::swim()
{
	cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里,哈哈!" << endl;
}
 
 
Turtle::Turtle(string theName):Animal(theName)//子类构造器定义
{
}
 
int main()
{
	Pig pig("小猪猪");
	Turtle turtle("小甲鱼");
 
	//子类继承基类中的方法
	pig.eat();
	turtle.eat();
	pig.eat(5);//重载
	turtle.eat(10);
	//子类自有的方法
	pig.climb();
	turtle.swim();
 
	system("pause");
	return 0;
}
/*一定要注意啊重载还是覆盖!*/

注意:继承后不可以重载!继承后的子类如果声明了基类中的函数(声明了函数名,后面的参数不管一不一样)都是覆盖,覆盖了之后,原本的基类的这个函数在子类中就无法被调用了,这里指的无法被调用是说主函数中,如果pig.eat(),那就是pig里的,而不是基类里的。当然子类的函数可以调用比如说在void Pig::eat()函数中调用Animal::eat()。

总的来说:覆盖优先

代码如下:

/*对继承下来的进行重载*/
#include <iostream>
using namespace std;
#include <string>
//基类(父类)动物
class Animal
{
public:
	//属性
	string mouth;
	
	Animal(string theName);
	//方法
	void eat();
	void sleep();
	void drool();
protected:
	string name;//这里定义name为保护的了,只有Animal本身以及其子类才可以修改
};
//子类1猪猪
class Pig : public Animal
{
public:
	void climb();
	Pig(string theName);//构造器
	void eat(int eatcount);//在继承基类中进行重载
};
//子类2王八
class Turtle : public Animal
{
public:
	void swim();
 
	Turtle(string theName);
};
Animal::Animal(string theName)
{
	name = theName;
}

//基类中方法定义
void Animal::eat()
{
	cout << "I am eating!!!" << endl;
}
void Animal::sleep()
{
	cout << "I am sleeping!!!" << endl;
}
void Animal::drool()
{
	cout << "I am drooling!!!" << endl;
}

//子类中方法定义
void Pig::climb()
{
	cout << "我是一只漂亮的小母猪,我会上树,我正在爬树!" << endl;
}

void Pig::eat(int eatcount)
{
	cout << "我吃了" << eatcount << "馄饨" << endl;
}
Pig::Pig(string theName) : Animal(theName)//子类构造器定义
{
}
void Turtle::swim()
{
	cout << "我是一只小甲鱼,当母猪想抓我的时候,我就游到海里,哈哈!" << endl;
}
 
 
Turtle::Turtle(string theName):Animal(theName)//子类构造器定义
{
}
 
int main()
{
	Pig pig("小猪猪");
	Turtle turtle("小甲鱼");
 
	//子类继承基类中的方法
	pig.eat();/*这里出错了,因为已经被有参数的覆盖了,继承后不能重载!!!,eat都没了*/
	turtle.eat();
	pig.eat(5);//重载
	//子类自有的方法
	pig.climb();
	turtle.swim();
 
	system("pause");
	return 0;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值