第二章 掌握C++
2.1 从结构到类
2.1.1 结构体的定义
- C++相比于C的特性:封装性、继承性、多态性;
- 对象具有状态和行为,状态保存在成员变量中,行为通过函数实现;
- 标准输入输出流对象:cin(>>)默认键盘 和 cout(<<)、cerr(<<)默认显示器;自动根据数据类型调整输入输出格式;
- 结构体中的函数称为成员函数。
程序2.1
#include <iostream>
using namespace std; //error C1083: 无法打开包括文件:“iostream.h”: No such file or directory
struct point
{
int x, y; //状态
void output() //行为
{
cout << "x="<< x << endl << "y=" << y << endl;//endl是换行符
}
}pt;//注意分号
int main()
{
cout << "请输入x和y的值:" << endl;
cin >> pt.x;
cin >> pt.y;
pt.output(); //注意括号
return 0;
}
2.1.2 结构体与类
在C++中,结构体(struct)和类(class)可以通用。
区别在于访问控制的差异:
- struct默认访问控制标识符public;public:可以在类的外部进行访问
- class默认访问控制标识符private;private:只能在类的内部进行访问
- protective。
程序2.2:将程序2.1中的struct point修改为class point
class point //类:抽象出一些事物共有的属性
{
public:
int x, y;
void output()
{
cout << "x="<< x << endl << "y=" << y << endl;
}
}pt; //实例化了一个对象==类的实例化:具有具体的属性值
2.2 C++的特性
2.2.1 类与对象
类:抽象出一些事物共有的属性;
对象:有具体的属性值。
2.2.2 构造函数
作用:在定义对象的同时,对成员变量进行初始化;
创建对象本身(分配内存)
规定:构造函数的名字和类名相同(唯一性);
没用返回值;
可以有参数。
注意:
- 如果一个类没有定义任何构造函数,那么C++会提供一个默认的不带参构造函数。而只要类中定义了一个构造函数,C++便不再提供任何其他构造函数;
- 每个类必须有一个构造函数,没有构造函数不能创建任何对象。构造函数如代码2.3所示。
2.2.3 析构函数
~类名(); //对象生命周期结束,释放其占用资源;是一个对象最后调用的成员函数
注意:析构函数不允许有返回值,更不允许有参数,并且一个类中只有一个构造函数。
代码2.3:构造函数和析构函数
class point //类名:point
{
public:
int x , y;
point() //构造函数 point()
{
x = 0; //在类中定义成员变量时,不能直接给其赋值(如int x=0;),而是在构造函数中赋值(P37提示)
y = 0;
}
~point() //析构函数 ~point()
{
}
};
2.2.4 函数的重载
条件:函数参数类型、个数不同才能构成重载。
注意:
- 只有函数的返回值类型不同,不能重载;
- 重载时,注意函数带有默认参数的情况。(P38)
- 对比覆盖:重载是发生在同一个类当中;覆盖是发生在父类和子类之间
代码2.4 函数的重载
class point
{
public:
int x , y;
point() //函数1
{
x = 0;
y = 0;
}
point(int a, int b) //重载,函数2
{
x = a;
y = b;
}
};
void main()
{
point pt(5,5); //C++根据参数类型和个数,执行函数2
}
2.2.5 this指针
- this指针是一个隐含的指针,指向对象本身,代表对象的地址。
- 用法:当形参的变量名和成员变量的变量名冲突时,可用this->来区别对哪一个变量进行赋值。(P40)
this->x是对成员变量进行赋值;
x是对形参赋值。
2.2.6 类的继承
2.2.6.1 继承
例如:class fish : public animal{};
- animal是父类,fish是子类。子类fish以public(公有)的方式继承父类animal。
- 子类除了自己的成员变量和成员方法外,还可以继承父类的成员变量和成员方法。
- 构造函数和析构函数的调用次序:
2.2.6.2 在子类中调用父类带参数的构造函数
例如:父类构造函数: animal(int h , int w){}
则在构造子类时,应该显式地去调用父类的带参数构造函数: fish():animal(400,300){}
2.2.6.3 类的继承及类中成员的访问特性
3种访问权限修饰符:
public:定义的成员可以在任何地方被访问;
protected:定义的成员只能在该类及其子类中访问;
private:定义的成员只能在该类自身中访问。==>不能被子类继承
3种继承方式:
2.2.6.4 多重继承
定义形式:class B: public C , public D
了解父类表顺序对调用构造函数和析构函数的影响。对于上面的例子,先构造C再构造D,先析构D再析构C。
2.2.7 虚函数与多态性、纯虚函数
2.2.7.1虚函数与多态性
程序 2.5
(P49)...
class animal
{
public:
...
void breathe(){cout<<"animal breathe"<<endl;} /*注意此时不是虚函数*/
};
class fish : public animal
{
public:
...
void breathe(){cout<<"fish bubble"<<endl;}
}
void breathetest(animal* pan)
{
pan->breathe();
}
void main()
{
animal* pan; //指向animal类的指针pan
fish fi;
pan = &fi; //将fish类的对象fi的地址直接赋给指向animal类的指针变量pan
breathetest(pan);
}
/*输出animal breathe*/
1、fish对象也是一个animal类,C++自动进行类型转换;反之,不能把animal对象看成fish对象。
2、当我们将fish转化为animal时,该对象就会被认为是原对象内存模型的上半部分。
3、将父类函数修改为虚函数,输出结果为fish bubble。
(P49)...
virtual void breathe() //虚函数
...
/*输出fish bubble*/
虚函数:用virtual关键字申明的函数–>多态性;
迟邦定:编译时并不确定具体调用的函数,而是在运行时依据对象的类型来确认调用的是哪一个函数。
概括(用法):在父类的函数前加上virtual关键字,在子类重写该函数,运行时将会根据对象的实际类型来调用相应的函数。
2.2.7.2 纯虚函数
- 纯虚函数不被具体实现,是抽象类,不能实例化对象。
- 纯虚函数可以让类先具有一个操作名称,而没有操作内容,在子类继承时再去具体定义。
写法:
virtual void breathe() = 0 ; //1、等于0;2、无函数体
注意:子类如果有对父类虚函数的覆盖定义,无论该覆盖定义是否有virtual,都是虚函数。
2.2.8函数的覆盖和隐藏
2.2.8.1 覆盖
1、覆盖(P52):发生在父类和子类之间 的 函数完全一样(函数名、参数列表),编译器根据实际类型确定要调用的函数。
2、对比重载:重载是发生在同一个类当中;覆盖是发生在父类和子类之间
程序2.6 函数的覆盖
class animal
{
public:
virtual void breathe()
{
cout<<"animal breathe"<<endl;
}
};
class fish : public animal
{
public:
void breathe() //覆盖
{
cout<<"fish bubble"<<endl;
}
};
void main()
{
fish fi;
fi.breathe();
}
/*输出fish bubble*/
2.2.8.2 隐藏
两种父类函数隐藏的情况:
- 子类和父类函数完全相同(函数名、参数列表),但父类函数没有virtual,则父类函数被隐藏。
- 子类和父类函数同名,但参数列表不同,则父类函数被隐藏。
区别隐藏与覆盖:覆盖是发生在父类与子类之间,两个函数必须完全相同且都是虚函数。否则就是隐藏。
2.2.9 引用
定义形式:
int a;
int &b = a; //引用必须在申明时就初始化,与a绑定;后面不会再与其他变量绑定
注意:
- 引用只是一个别名,不占用内存空间;此处要和指针区分开来。
- 引用多用于函数的形参定义。如程序2.4所示。
程序2.7 引用示例
#include <iostream>
using namespace std; //换成引用意思表达更清晰:
/*以下为用指针交换数据*/
void change(int* a,int* b); //void change(int& a,int& b);
int main()
{
int x = 5, y = 3;
cout << "x=" << x << endl;
cout << "y=" << y << endl;
change(&x,&y); //是对x和y的地址互换还是对x和y互换? change(x,y);
cout << "x=" << x << endl;
cout << "y=" << y << endl;
return 0;
}
void change(int* a, int* b) //void change(int a,int b)
{ //{
int c; // int c;
c = *a; // c = a;
*a = *b; // a = b;
*b = c; // b = c;
} //}
2.2.10 C++类设计习惯和头文件重复包含问题的解决
1、头文件存放类的定义及类成员函数的声明;
源文件存放类中成员函数的实现。
2、注意文件包含;
#include "animal.h" //"",从当前目录开始搜索,然后是系统目录和PATH环境变量所列出的目录
#include <iostream> //<>,从系统目录开始搜索,然后是PATH环境变量所列出的目录。``不搜索当前目录
注意子类的头文件中也要包含父类的头文件,如fish.h中的#include “animal.h”。
3、::是作用域表示符,表明函数或数据成员属于哪个类;
前面如果不跟类名,表示全局函数(即非成员函数)或全局数据。
4、在头文件中声明函数时用了virtual,在源文件中就不用写virtual。
5、要解决类重复定义的问题,要使用条件预处理指令(如课后程序animal.h和fish.h所示)
程序2.8 条件预处理指令解决类重复定义的问题
#ifndef ANIMAL_H_H //如果没有定义ANIMAL_H_H,就定义ANIMAL_H_H,接着申明animal类
//如果定义过了ANIMAL_H_H,直接跳至endif
#define ANIMAL_H_H
class animal
{
};
#endif
2.2.11 VC++程序编译连接的原理与过程
1、编译:头文件不参与编译
源文件单独编译
XXX.obj目标文件
2、链接
编译链接过程如下。
课后程序
animal.h
/*animal.h*/
#ifndef ANIMAL_H_H //如果没有定义ANIMAL_H_H,就定义ANIMAL_H_H,接着申明animal类
//如果定义过了ANIMAL_H_H,直接跳至endif
#define ANIMAL_H_H
class animal
{
public:
animal();
~animal();
void eat();
void sleep();
virtual void breathe();
};
#endif
animal.cpp
/*animal.cpp*/
#include <iostream>
#include "animal.h"
using namespace std;
animal::animal()
{
}
animal::~animal()
{
}
void animal::eat()
{
}
void animal::sleep()
{
}
void animal::breathe() //在头文件中有virtual,则在源文件中不用加virtual
{
cout << "animal breathe" << endl;
}
fish.h
/*fish.h*/
#include "animal.h" //fish类从animal类继承
#ifndef FISH_H_H
#define FISH_H_H
class fish :public animal //结构体、类名后面没有括号()
{
public:
void breathe();
};
#endif
fish.cpp
/*fish.cpp*/
#include <iostream>
#include "fish.h"
using namespace std;
void fish::breathe()
{
cout << "fish bubble" << endl;
}
main.cpp
/*main.cpp*/
#include "animal.h"
#include "fish.h"
void breathetest(animal* pan)
{
pan->breathe();
}
int main()
{
animal* pan;
fish fi;
animal an;
pan = &fi;
breathetest(pan);
pan = &an;
breathetest(pan);
return 0;
}
运行结果
问题及反思
vs2019编程出现的问题:
1、建立C++空项目后,要在右侧工程的源文件的文件夹下创建XXX.cpp文件;
否则编译时无法启动程序XXX.exe,系统找不到指定文件。
2、头文件#include <iostream.h>后添加using namespace std;
否则报错:error C1083: 无法打开包括文件:“iostream.h”: No such file or directory
3、定义结构体或类时,别忘记最后的分号;结构体名或类名后面没有小括号;
4、引用成员函数别忘记括号:pt.output();
5、.sln为项目文件
参考文献
[1]https://blog.csdn.net/weixin_43751983/article/details/91147918this指针
[2]孙鑫.VC++深度详解修订版[M]. 北京:电子工业出版社, 2012. 27-62.