自助建站网站,泰州网站制作工具,化妆品网站设计论文,wordpress优化nginx面向对象的C 类与对象类与对象的概念类的封装性构造函数析构函数静态数据成员静态成员函数对象的存储空间类模板析构函数与构造函数的执行顺序 继承与派生继承与派生的一般形式派生类的访问属性派生类的构造函数与析构函数#xff08;看的还不够仔细#xff09;派生类的构造函… 面向对象的C 类与对象类与对象的概念类的封装性构造函数析构函数静态数据成员静态成员函数对象的存储空间类模板析构函数与构造函数的执行顺序 继承与派生继承与派生的一般形式派生类的访问属性派生类的构造函数与析构函数看的还不够仔细派生类的构造函数与析构函数的调用顺序 类的多态多态虚函数的使用纯虚函数析构函数 类与对象
类与对象的概念
类是抽象的不占用存储空间。
class默认是private的stuct里面默认是public。
C语言中struct不能定义成员函数但是在C中增加了class类型后扩展了struct的功能struct中也能定义成员函数了。
比如
struct Student{public:void play(){}private:int num;
}好的习惯每一种成员访问限定符在类体中只出现一次并且先写public部分把private部分放在类体的后部这样可以使得用户将注意力集中在能被外界调用的成员上使得阅读者的思路更加清晰。
类的封装性
受保护成员访问权限允许类成员和派生类成员访问不运行类外的任何访问。比如private不能直接a.这样调用必须调用函数才行。
为了实现信息隐藏会把类成员函数的定义放在另一个文件中而不放在头文件中。
构造函数
如果用户自己没有定义构造函数那么C系统就会自动为其生成一个构造函数只是这个构造函数的函数体是空的什么也不做当然也不进行初始化。
C提供另一种初始化数据成员的方法参数初始化表。这种方法不在函数体内对数据成员初始化而是在函数首部实现。
Circle::Circle(int r):radius(r){}一个类中如果定义了全是默认参数的构造函数后就不能再定义重载构造函数了因为不知道调用的是谁了
析构函数
static局部对象在函数调用结束时对象不释放所以也不执行析构函数。只有在main函数结束或调用exit函数结束程序时才调用static局部对象的析构函数。
如果用户没有编写析构函数编译系统会自动生成一个默认的析构函数但不进行任何操作所以许多简单的类中没有用显式的析构函数。
静态数据成员
全局数据可以被任何人修改而且在一个项目中它很容易和其他名字冲突。
如果一个静态数据成员被声明而没有被定义链接器回报高一个错误定义必须出现在类的外部而且只能定义一次。所以静态数据成员的声明通常会放在一个类的实现文件中。
比如
xxx.h文件中
class base{public:static int var;
};xxx.cpp类型文件中
int base::var10;在头文件中定义初始化静态成员容易引起重复定义的错误比如这个头文件同时被多个.cpp文件所包含的时候。即使加上#ifndef#define#endif或者#pragma once也不行。
C静态数据成员被类的所有对象所共享包括该类的派生类的对象。
如果在一个函数中定义了静态变量在函数结束时该静态变量并不被释放仍然存在并保留其值。
静态数据成员类似它不随对象建立而分配空间也不随对象的撤销而释放。是程序在编译时被分配空间到程序结束时释放空间。
静态成员函数
静态成员函数的作用不是为了对象之间的沟通而是为了能处理静态数据成员。
当调用一个对象的非静态成员函数时系统会把该对象的起始地址赋给成员函数的this指针。
比如
int Box::volume()
{return height*width*length;
}C会处理为
int Box::volume()
{return this-height*this-width*this-length;
}而this地址会在调用时赋值比如
int main()
{Box shit;shit.volume();//shit对象地址赋值给this指针
}而静态成员函数并不属于某一对象它与任何对象都无关因此静态成员函数没有this指针。所以它无法访问非静态成员。
静态成员函数和非静态成员函数的根本区别就是一个有this指针另一个没有。
好的习惯只用静态成员函数引用静态数据成员而不引用非静态数据成员。
对象的存储空间
一个对象占用空间非静态成员变量总和加上编译器为了CPU计算做出的数据对齐处理这种对齐是为了寻址吗不对齐的话可能增加寻址次数我的理解是每次CPU找数据都只能0-64,65-128这种如果不对齐可能32-72存储就导致至少寻址两次和支持虚函数所产生的负担的总和。
空类存储空间
#includeiostream
using namespace std;
class CBox{};
int main(){CBox boxobj;coutsizeof(boxobj)endl;return 0;
}打印结果1字节。
只有成员变量的类存储空间
#includeiostream
using namespace std;
class CBox{int length,width,height;
};
int main(){CBox boxobj;coutsizeof(boxobj)endl;return 0;
}结果12字节。
静态数据成员不占对象的内存空间单独存放在其他地方。
成员函数不占空间只有虚函数会被放到虚函数表中。
构造函数和析构函数不占空间
类中有虚析构函数的空间计算
#includeiostream
using namespace std;
class CBox{public:CBox(){};virtual ~CBox(){};
};
int main(){CBox boxobj;coutsizeof(boxobj)endl;return 0;
}结果是8
编译器为了支持虚函数会产生额外的负担即指向虚函数表的指针的大小指针变量在64位机器占8字节。类有一个或者多个虚函数都相当于有指针8字节。
单一继承和多重继承空间都是1.
虚继承对象占8字节。
函数代码是存储在对象空间之外的。而且函数代码段是公用的如果对同一个类定义了10个对象这些对象的成员函数对应的是同一个函数代码段。
每个成员函数都有this指针值为被调用成员函数的所属对象的起始地址。比如调用a.volume时编译系统把对象a的起始地址赋给this指针在成员函数引用数据成员时就按照this的指向找到对象a的数据成员。
.优先级高于*
全局函数不能使用this指针。
this指针会因编译器不同而有不同的存储位置可能是栈寄存器或全局变量。
类模板
例子
templateclass Tclass Operation{public:Operation(T a,T b):x(a),y(b){}T add(){return xy;}T subract(){return x-y;}private:T x,y;};声明一个类模板的对象时要用实际类型名去取代虚拟的类型
比如
Operation int opobj(1,2);如果类模板的成员函数是在类外定义的则需要这么写
templateclass TT Operation T ::add(){return xy;}析构函数与构造函数的执行顺序
一个函数内先调用析构函数的次序正好与调用构造函数的次序相反。
比如
#includeiostream
using namespace std;
class CBox{public:CBox(int a){cout构造了aendl};~CBox(){cout析构了aendl};private:int a;
};
int main(){CBox box1(1);CBox box2(2);return 0;
}打印结果
构造了1构造了2析构了2析构了1继承与派生
继承要慎用一般是在程序开发过程中重构得到的不是程序设计之初就使用继承。优先使用组合而不是继承。
继承与派生的一般形式
不写继承形式默认为私有。
派生类有两大部分内容从基类继承而来的和在声明派生类时增加的部分。派生类中接受了基类的全部内容。
可能出现有些基类的成员派生类用不到造成数据冗余多次派生后就存在大量无用的数据不仅浪费空间而且在对象的建立赋值复制和参数的传递中花费许多无谓的空间降低效率。实际开发中要慎重选择基类。使冗余量最小
派生类的访问属性
公用继承基类的公用成员和保护成员在派生类中保持原有访问属性其私有成员仍为基类私有
私有继承基类的公用成员和保护成员在派生类中成了私有成员私有成员仍为基类私有
受保护的继承基类的公用成员和保护成员在派生类中成了保护成员不能被外界引用但可以被派生类的成员引用其私有成员仍为基类私有。
P52图
无论哪一种继承方式在派生类中是不能访问基类的私有成员的私有成员只有被本类的成员函数所访问毕竟派生类与基类不是同一个类。
如果多级派生都采用公用继承方式那么直到最后一级派生类都能访问基类的公用成员和保护成员。
如果采用私有继承方式经过若干次派生之后基类的所有成员都会变成不可访问的了。
如果采用保护继承方式在派生类外是无法访问派生类中的任何成员的而且经过多次派生后人们很难清楚记得哪些成员能访问哪些不能很容易出错。
所以实际中最常用是公用继承。
派生类的构造函数与析构函数看的还不够仔细
P53-54
派生时派生类不能继承基类的析构函数也需要通过派生类的析构函数去调用基类的析构函数。
执行派生类的析构函数时系统会自动调用基类的析构函数和子对象的析构函数对基类和子对象进行清理。
派生类的构造函数与析构函数的调用顺序
构造函数调用顺序
基类构造函数成员类对象的构造函数如果有多个成员类对象则构造函数调用顺序是对象在类中被声明的顺序派生类构造函数
析构函数调用顺序相反
首先调用派生类的析构函数其次再调用成员类对象的析构函数最后基类的析构函数。
例子
class CBase{public:CBase(){std::coutCBase::CBase()std::endl;}~ CBase(){std::coutCBase::~CBase()std::endl;}
};class CBase1:public CBase{public:CBase1(){std::coutCBase::CBase1()std::endl;}~ CBase1(){std::coutCBase::~CBase1()std::endl;}
};class CDerive{public:CDerive(){std::coutCDerive::CDerive()std::endl;}~ CDerive(){std::coutCDerive::~CDerive()std::endl;}
};class CDerive1:public CBase1{private:CDerive m_derive;public:CDerive1(){std::coutCDerive1::CDerive1()std::endl;}~ CDerive1(){std::coutCDerive1::~CDerive1()std::endl;}
};int main()
{CDerive1 derive;return 0;
}程序执行结果
CBase::CBase()//最高层基类
CBase::CBase1()//第二层基类
CDerive::CDerive()//成员对象构造函数
CDerive1::CDerive1()//派生类构造函数//下面顺序就是相反的
CDerive1::~CDerive1()
CDerive::~CDerive()
CBase::CBase1()
CBase::CBase()析构函数在下面3种情况下调用
delete指向对象的指针时或delete指向对象的基类类型指针而其基类虚构函数是虚函数时
对象i是对象o的成员o的析构函数被调用时对象i的析构函数也被调用。
类的多态
多态
C中多态性是指具有不同功能的函数可以用同一个函数名。
面向对象中向不同的对象发送同一个消息不同的对象在接收时会产生不同的行为。
虚函数的作用是允许在派生类中重新定义与基类同名的函数并且可以通过基类指针或引用来访问基类和派生类的同名函数。
比如
class A{public: virtual void foo(){coutaendl;}
}class B:public A{public: void foo(){coutbendl;}
}int main(){A a;B b;A *c;ca;c-foo();cb;c-foo();
}运行结果
a
b将基类A中的成员函数foo定义为虚函数就能使得基类对象的指针变量既可以访问基类的成员函数foo也可以访问派生类的成员函数foo。
基类指针本来是用来指向基类对象的如果用它指向派生类对象则需要进行指针类型转换即将派生类对象的指针先转换为基类的指针所以基类指针指向的是派生类对象中的基类部分。
如果基类中的display函数不是虚函数是无法通过基类指针去调用派生类对象中的成员函数的。
虚函数突破了这个限制在派生类的基类部分中派生类的虚函数取代了基类原来的虚函数因此在使基类指针指向派生类对象后调用虚函数时就调用了派生类的虚函数。
注意的是只有用virtual声明了虚函数后才能这样如果不声明为虚函数企图通过基类指针调用派生类的非虚函数是不行的。
基类成员函数声明为虚函数后派生类的同名函数自动成为虚函数但最好还是加上virtual如果派生类没有对基类的虚函数重新定义则派生类简单地继承其直接基类的虚函数可以通过指向基类的指针指向同一类族中不同类的对象从而调用其中的同名函数。
类外能定义虚函数时不必再加virtual关键字。
如果用派生类指针调用该成员函数这不是多态行为没有用到虚函数的功能。
基类中定义的非虚函数有时会在派生类被重新定义如果用基类指针调用该成员函数则调用的是基类部分的成员函数。
虚函数的使用
类外普通函数不能是虚函数它只用于继承体系
有时在定义虚函数时并不定义其函数体。此时作用只是定义了一个虚函数名具体功能留给派生类去添加。
使用虚函数系统要有一定的空间开销当一个类带有虚函数时编译系统会为该类构造一个虚函数表它是一个指针数组用于存放每个虚函数的入口地址。系统在进行动态关联时的时间开销是很少的因此多态是高效的。
纯虚函数
它是在基类中声明的虚函数在基类中没有定义但要求任何派生类都要定义自己的实现方法。
实现纯虚函数方法函数原型后面加0。
一个类含有纯虚函数那么该类是抽象类不能实例化。
析构函数
派生类对象由一个基类指针删除而基类有非虚函数的析构函数会导致对象的派生成分没被销毁掉。
基类的析构函数应该是virtual的。