目录
面向过程程序设计
面向对象程序设计的基本特征:抽象、封装、继承、多态。
//强制类型转换
const修饰符
?函数重载
内联函数
构造函数
析构函数
拷贝构造函数
this指针
静态成员
友元
继承和派生
面向过程程序设计
对象:描述其属性的数据以及对这些数据施加的一组操作封装在一起构成的统一体。对象可认为是数据+操作。
类:类是具有相同的数据和相同的操作的一组对象的集合。
消息传递:对象之间的交互。
面向对象程序设计的基本特征:抽象、封装、继承、多态。
简单概括三大特性作用: ? ? 封装是为了代码模块化和增加安全性 ? ? 继承是为了重用和扩展现有的代码模块 ? ? 多态是为了接口复用
抽象:对具体对象(问题)进行概括,抽出这一类对象的公共性质并加以描述的过程。抽象的实现:通过类的声明。
封装: ? ? 保护数据成员,不让类以外的程序直接访问或者修改类的成员,只能通过其成员对应方法访问(即数据封装) ? ? 隐藏方法实现的具体细节,仅仅提供接口, 内容修改不影响外部调用(即方法封装) ? ? ? ?? 继承: ? ? 三种继承方式:public、protected、private。 ? ? 继承的目的: 重用代码,一个类B继承另一个类A,则B就继承了A中申明的成员以及函数 ? ? 派生的目的: 代码扩展,继承自一个类然后添加自己的属性和方法则实现代码的扩展 缺陷: ? ? 父类变化了子类必须变化 增加了耦合性 ? ? ? ? ? ? ? ?? 多态: ? ? 接口的复用,一个接口多种实现 ? ? 用父类型别的指针指向其子类的实例,然后通过父类的指针调用实际子类的成员函数
C++多态性是通过虚函数来实现的, 虚函数允许子类重新定义成员函数,而子类重新定义父类的做法称为覆盖或者重写 函数重载为允许多个不同名称的函数,这些函数函数列表不同或者函数个数不同或者两者都不同 ? ?? 编译时多态:主要指方法的重载 (在编译器编译期间就可以确定函数的调用地址,并产生代码,是静态的) 运行时多态:通过虚函数实现 ? ? (在运行时确定的调用地址) ?
#include <iostream> //编译预处理命令
using namespace std; //使用命名空间
int add(int a, int b); //函数原型说明
int main() //主函数
{
int x, y;
cout << "Enter two numbers: " << endl;//标准输入流cin
cin >> x;
cin >> y;
int sum = add(x, y);
cout << "The sum is : " << sum << '\n';//标准输出流cout
return 0;
}
int add(int a, int b) //定义add()函数,函数值为整型
{
return a + b;
}
结构名、联合名、枚举名都可直接作为类型名,在定义变量时,不用再在结构名、联合名、枚举名前面冠以struct、union、enum关键字 strcut ?Student { ? ? ?int age; }; //C语言写法:struct Student ?stu; //C++写法:Student stu;
//强制类型转换
C语言中要把整型数据转换成浮点型数据 ?int i=10; ?float f=(float)i; C++中可以把类型名作为函数名使用 int i=10; ?float f=float(i);
在声明函数原型或函数定义时,为一个或多个参数指定缺省值 int func(int x=5,float y=2.3); 下面的调用都是正确的: ? ? func(); ? ? ? ? ? ? //使用缺省值 ? ? func(10); ? ? ? ? ?//x使用实参值10,y使用缺省值2.3 ? ? func(3,4.5); ? ? ? // x和y分别使用实参值3,4.5?
const修饰符
在C语言中,习惯使用#define 来定义常量,例如#define PI 3.14 ,C++提供了一种更灵活、更安全的方式来定义常量,即使用const 修饰符来定义常量。例如const float PI = 3.14;
修饰指针所指向的变量,将该变量定义为常量 ? ? ? ? 示例语句:const char *str=&c; 将指针本身修饰为常指针,指针所指向的变量仍为变量 ? ? ? ? 示例语句: char *const str=&c; 指针本身修饰为常指针,指针所指向的变量修饰为常量 ? ? ? ? 示例语句: const char *const str=&c;
?函数重载
?函数重载 ? ? 同一个函数名对应不同的函数实现,每一类实现对应着一个函数体,名字相同,功能相同,只是参数的类型或参数的个数不同。 多个同名函数只是函数类型(函数返回值类型)不同时,它们不是重载函数
int add(int a,int b)
{
?? ?return a+b;
}
double add(double a,double b)
{
?? ?return a+b;
}
int add(int a,int b,int c)
{
?? ?return a+b+c;
}
?
内联函数
在函数名前冠以关键字inline ,该函数就被声明为内联函数。每当程序中出现对该函数的调用时,C++编译器使用函数体中的代码插入到调用该函数的语句之处,同时使用实参代替形参,以便在程序运行时不再进行函数调用。引入内联函数主要是为了消除调用函数时的系统开销,以提高运行速度。
说明:
- 内联函数在第一次被调用之前必须进行完整的定义,否则编译器将无法知道应该插入什么代码
- 在内联函数体内一般不能含有复杂的控制语句,如for语句和switch语句等
- 使用内联函数是一种空间换时间的措施,若内联函数较长,较复杂且调用较为频繁时不建议使用
构造函数
是一种特殊的成员函数,主要功能是为对象分配存储空间,以及为类成员变量赋初值
- 构造函数名必须与类名相同
- 没有任何返回值和返回类型
- 创建对象自动调用,不需要用户来调用,且只掉用一次
- 类没有定义任何构造函数,编译系统会自动为这个类生成一个默认的无参构造函数
构造函数定义
析构函数
是一种特殊的成员函数,当对象的生命周期结束时,用来释放分配给对象的内存空间爱你,并做一些清理的工作。
析构函数名与类名必须相同。 析构函数名前面必须加一个波浪号~。 没有参数,没有返回值,不能重载。 一个类中只能有一个析构函数。 没有定义析构函数,编译系统会自动为和这个类生成一个默认的析构函数。 析构函数的定义: //1.类中定义 2.类中声明,类外定义 [类名::]~析构函数名() { ?? ?函数体; } ?
#include <iostream>
using namespace std;
class A {
public:
A() { cout << "A::A" << endl; }
~A() { cout << "A~A" << endl; }
void run(void) {
cout << "run()" << endl;
}
void run(int a) {
cout << "run(A)" << endl;
}
};
class B : public A {
public:
B() { cout << "B::B" << endl; }
~B() { cout << "B::~B" << endl; }
void run(int a) {
cout << "run(B)" << endl;
}
};
int main(void) {
B *b=new B;
delete b;
//b.run(0); //语句1
//b.A::run(1); //语句2
//b.A::run(); //语句4
return 0;
}
拷贝构造函数
拷贝构造函数是一个特殊的构造函数,其作用是用一个已经存在的对象初始化本类的新对象。可根据自己的需要定义拷贝构造函数,也可由系统生成一个缺省的拷贝构造函数。拷贝构造函数没有返回值。拷贝构造函数名与类名相同,但参数是本类对象的引用。 自定义拷贝构造函数 ? ? 自定义拷贝构造函数的一般形式为: ? ? 类名(类名&对象名) ? ? { ? ? ? ?//拷贝构造函数的函数体 ? ? } ? ? 其中,对象名是用来初始化另一个对象的对象的引用。
this指针
每个成员函数都有一个特殊的指针this,它始终指向当前被调用的成员函数操作的对象
指针this是系统自动生成的、隐含于每个对象中的指针。当一个对象生成以后,系统就为这个对象定义了一个this指针,它指向这个对象的地址。也就是说,每一个成员函数都有一个this指针,当对象调用成员函数时,该成员函数的this指针便指向这个对象。这样,当不同的对象调用同一个成员函数时,编译器将根据该成员函数的this指针指向的对象确定引用哪个对象的成员函数。因此,成员函数访问数据成员的实际形式为: ? ? ? ? ? ? this->成员变量
静态成员函数
以关键字static开头的成员为静态成员,多个类共享。
- static 成员变量属于类,不属于某个具体的对象
- 静态成员函数只能访问类中静态数据成员
静态数据成员
静态数据成员 //类内声明,类外定义 class xxx { ?? ?static 数据类型 静态数据成员名; } 数据类型 类名::静态数据成员名=初值 //访问 类名::静态数据成员名; 对象名.静态数据成员名; 对象指针名->静态数据成员名;
静态成员函数
在类定义中,前面有static说明的成员函数称为静态成员函数。静态成员函数属于整个类,是该类所有对象共享的成员函数,而不属于类中的某个对象。静态成员函数的作用不是为了对象之间的沟通,而是为了处理静态数据成员。定义静态成员函数的格式如下:
//类内声明,类外定义 class xxx { ?? ?static 返回值类型 静态成员函数名(参数列表); } 返回值类型 类名::静态成员函数名(参数列表) { ?? ?函数体; } //访问 类名::静态成员函数名(参数列表); 对象名.静态成员函数名(参数列表); 对象指针名->静态成员函数名(参数列表); ?
友元
借助友元(friend),可以使得其他类中得成员函数以及全局范围内得函数访问当前类得private成员。? 友元函数
友元函数不是类的成员函数,所以没有this指针,必须通过参数传递对象。 友元函数中不能直接引用对象成员的名字,只能通过形参传递进来的对象或对象指针来引用该对象的成员。 ?
//1.将非成员函数声明为友元函数
class Person
{
public:
Person(int = 0,string = "张三");
friend void show(Person *pper);//将show声明为友元函数
private:
int age;
string name;
};
Person::Person(int a, string s):age(a),name(s)
{
cout << a << " " << s << endl;
}
void show(Person *pper)
{
cout << "age="<< pper->age << endl;
cout << "name=" << pper->name << endl;
}
int main()
{;
Person *pp = new Person(234,"yar");
show(pp);
system("pause");
return 0;
}
//2.将其他类的成员函数声明为友元函数
//person中的成员函数可以访问MobilePhone中的私有成员变量
class MobilePhone;//提前声明
//声明Person类
class Person
{
public:
Person(int = 0,string = "张三");
void show(MobilePhone *mp);
private:
int age;
string name;
};
//声明MobilePhone类
class MobilePhone
{
public:
MobilePhone();
friend void Person::show(MobilePhone *mp);
private:
int year;
int memory;
string name;
};
MobilePhone::MobilePhone()
{
year = 1;
memory = 4;
name = "iphone 6s";
}
Person::Person(int a, string s):age(a),name(s)
{
cout << a << " " << s << endl;
}
void Person::show(MobilePhone *mp)
{
cout << mp->year << "年 " << mp->memory << "G " << mp->name << endl;
}
int main()
{
Person *pp = new Person(234,"yar");
MobilePhone *mp = new MobilePhone;
pp->show(mp);
system("pause");
return 0;
}
友元类 当一个类为另一个类的友元时,称这个类为友元类。?友元类的所有成员函数都是另一个类中的友元成员。 语法形式:friend [class] 友元类名
继承和派生
继承就是在一个已有类的基础上建立一个新类,已有的类称基类或父类,新建立的类称为派生类和子类;派生和继承是一个概念,角度不同而已,继承是儿子继承父亲的产业,派生是父亲把产业传承给儿子。
继承方式:
public-基类的public成员和protected成员的访问属性保持不变,私有成员不可见。 private-基类的public成员和protected成员成为private成员,只能被派生类的成员函数直接访问,私有成员不可见。 protected-基类的public成员和protected成员成为protected成员,只能被派生类的成员函数直接访问,私有成员不可见。 ?
1) 公有继承(public)
公有继承的特点是基类的公有成员和保护成员作为派生类的成员时,它们都保持原有的状态,而基类的私有成员仍然是私有的,不能被这个派生类的子类所访问。
(2)私有继承(private)
私有继承的特点是基类的公有成员和保护成员都作为派生类的私有成员,并且不能被这个派生类的子类所访问。
(3)保护继承(protected)
保护继承的特点是基类的所有公有成员和保护成员都成为派生类的保护成员,并且只能被它的派生类成员函数或友元访问,基类的私有成员仍然是私有的。
虚函数
实现程序多态性的一个重要手段,使用基类对象指针访问派生类对象的同名函数。
将基类中的函数声明为虚函数,派生类中的同名函数自动为虚函数。 声明形式:virtual 函数类型 函数名 (参数列表); 构造函数不能声明为虚函数,析构函数可以声明为虚函数。
class ?A
{
public:
?? ?virtual void show()
?? ?{
?? ??? ?cout << "A show" << endl;
?? ?}
};
class B: ?public A
{
public:
?? ?void show()
?? ?{
?? ??? ?cout << "B show" << endl;
?? ?}
};
int main()
{
?? ?
?? ?B b;
?? ?b.show();//B show
?? ?A *pA = &b;
?? ?pA->show();//B show 如果show方法前没用virtual声明为虚函数,这里会输出A show
?? ?
?? ?system("pause");
?? ?return 0;
}
纯虚函数
在基类中不执行具体的操作,只为派生类提供统一结构的虚函数,将其声明为虚函数。
class ?A
{
public:
?? ?virtual void show() = 0;
};
class B: ?public A
{
public:
?? ?void show()
?? ?{
?? ??? ?cout << "B show" << endl;
?? ?}
};
抽象类:包含纯虚函数的类称为抽象类。由于纯虚函数不能被调用,所以不能利用抽象类创建对象,又称抽象基类。 ?
?什么函数不能声明为虚函数?
一个类中将所有的成员函数都尽可能地设置为虚函数总是有益的。? 但设置虚函数须注意:? 1:只有类的成员函数才能说明为虚函数;?
原因:普通函数(非成员函数)只能被overload,不能被override,声明为虚函数也没有什么意思,因此编译器会在编译时邦定函数。
2:静态成员函数不能是虚函数;
?因为static属于class自己的,也必须有实体; 没有this指针,它无法进行对象的判别。
3:内联函数不能为虚函数;
原因: inline是编译时展开,必须有实体;内联函数就是为了在代码中直接展开,减少函数调用花费的代价,虚函数是为了在继承后对象能够准确的执行自己的动作,这是不可能统一的。(再说了,inline函数在编译时被展开,虚函数在运行时才能动态的邦定函数)
4:构造函数不能是虚函数;? 原因:
1. 从存储空间角度,虚函数对应一个指向vtable虚函数表的指针,可是这个指向vtable的指针其实是存储在对象的内存空间的。如果构造函数是虚的,就需要通过 vtable来调用,可是对象还没有实例化,也就是内存空间还没有,怎么找vtable呢?所以构造函数不能是虚函数。 2. 从使用角度,虚函数主要用于在信息不全的情况下,能使重载的函数得到对应的调用。构造函数本身就是要初始化实例,那使用虚函数也没有实际意义。所以构造函数没有必要是虚函数。虚函数的作用在于通过父类的指针或者引用来调用它的时候能够变成调用子类的那个成员函数。而构造函数是在创建对象时自动调用的,不可能通过父类的指针或者引用去调用,因此也就规定构造函数不能是虚函数。 3. 构造函数不需要是虚函数,也不允许是虚函数,因为创建一个对象时我们总是要明确指定对象的类型,尽管我们可能通过实验室的基类的指针或引用去访问它但析构却不一定,我们往往通过基类的指针来销毁对象。这时候如果析构函数不是虚函数,就不能正确识别对象类型从而不能正确调用析构函数。 4. 从实现上看,vbtl在构造函数调用后才建立,因而构造函数不可能成为虚函数从实际含义上看,在调用构造函数时还不能确定对象的真实类型(因为子类会调父类的构造函数);而且构造函数的作用是提供初始化,在对象生命期只执行一次,不是对象的动态行为,也没有必要成为虚函数。 5. 当一个构造函数被调用时,它做的首要的事情之一是初始化它的VPTR。因此,它只能知道它是“当前”类的,而完全忽视这个对象后面是否还有继承者。当编译器为这个构造函数产生代码时,它是为这个类的构造函数产生代码——既不是为基类,也不是为它的派生类(因为类不知道谁继承它)。所以它使用的VPTR必须是对于这个类的VTABLE。而且,只要它是最后的构造函数调用,那么在这个对象的生命期内,VPTR将保持被初始化为指向这个VTABLE, 但如果接着还有一个更晚派生的构造函数被调用,这个构造函数又将设置VPTR指向它的 VTABLE,等.直到最后的构造函数结束。VPTR的状态是由被最后调用的构造函数确定的。这就是为什么构造函数调用是从基类到更加派生类顺序的另一个理由。但是,当这一系列构造函数调用正发生时,每个构造函数都已经设置VPTR指向它自己的VTABLE。如果函数调用使用虚机制,它将只产生通过它自己的VTABLE的调用,而不是最后的VTABLE(所有构造函数被调用后才会有最后的VTABLE)。 5:析构函数可以是虚函数,而且通常声明为虚函数。? 原因:析构函数执行时先调用派生类的析构函数,其次才调用基类的析构函数。构造一个CStudent的动态对象如果析构函数不是虚函数,而程序执行时又要通过基类的指针去销毁派生类的动态对象,那么用delete销毁对象时,只调用了基类的析构函数,未调用派生类的析构函数。这样会造成销毁对象不完全。类析构函数要声明为虚函数这样派生类调用析构函数才能层层回调,释放资源。这也是虚函数的作用--提供回调的指针。 ?
|