C++期末
基本概念
面向对象编程的三大特征
1)封装2)继承3)多态
虚函数、重载函数特点,有哪些约束
虚函数是动态多态,重载函数是静态多态。
重载函数之间要求函数返回值、函数名一致,但是参数不一致。
虚函数要求函数返回值、函数名、参数都一致。
纯虚函数作用?派生关系?如何实现?
一、定义
纯虚函数是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
virtual void funtion()=0
二、引入原因
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理。
为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function()= 0;),则编译器要求在派生类中必须予以重写以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
三、相似概念
1、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a、编译时多态性:通过重载函数实现
b、运行时多态性:通过虚函数实现。
2、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态覆盖(Override)
3、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
派生类访问基类成员有哪些访问控制?对象呢?
区分“派生类对象”和“派生类”对基类成员的访问权限。
“派生类对象”对基类成员的访问权限:
(1)对于公有继承,只有基类的公有成员可以被“派生类对象”访问,其他(保护和私有)成员不能被访问。
(2)对于私有继承和保护继承,基类中所有成员都不能被“派生类对象”访问。
“派生类”对基类中成员的访问权限:
(1)对于公有继承,基类中的公有成员和保护成员可以被“派生类”访问,而基类中的私有成员不能被访问。
(2)对私有继承和保护继承,也是基类中的公有成员和保护成员可以被“派生类”访问,而基类中的私有成员不能被访问。
多态性如何实现?
、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。
a、编译时多态性:通过重载函数实现
b、运行时多态性:通过虚函数实现。
类和对象关系?
每一个实体都是对象。有一些对象是具有相同的结构和特性的。每个对象都属于一个特定的类型,这个特定的类型称为类(class )。
类代表了某一批对象的共性和特征。前面已说明:类是对象的模板,而对象是类的具体实例(instance)。
正如同结构体类型和结构体变量的关系一样,需要先声明一个结构体类型,然后用它去定义结构体变量。同一个结构体类型可以定义出多个不同的结构体变量。
在C++中也是先声明一个类类型,然后用它去定义若干个同类型的对象。对象就是类类型的一个变量。可以说类是对象的模板,是用来定义对象的一种抽象类型。
类是抽象的,不占用内存,而对象是具体的,占用存储空间。
深拷贝、浅拷贝区别?
1. 深拷贝是指源对象与拷贝对象互相独立,其中任何一个对象的改动都不会对另外一个对象造成影响。举个例子,一个人名叫张三,后来用他克隆(假设法律允许)了另外一个人,叫李四,不管是张三缺胳膊少腿还是李四缺胳膊少腿都不会影响另外一个人。比较典型的就是Value(值)对象,如预定义类型Int32,Double,以及结构(struct),枚举(Enum)等。
2. 浅拷贝是指源对象与拷贝对象共用一份实体,仅仅是引用的变量不同(名称不同)。对其中任何一个对象的改动都会影响另外一个对象。举个例子,一个人一开始叫张三,后来改名叫李四了,可是还是同一个人,不管是张三缺胳膊少腿还是李四缺胳膊少腿,都是这个人倒霉。比较典型的就有Reference(引用)对象,如Class(类)。
深拷贝和浅拷贝的区别是在对象状态中包含其它对象的引用的时候,当拷贝一个对象时,如果需要拷贝这个对象引用的对象,则是深拷贝,否则是浅拷贝。
抽象类作用?
将不用来定义对象而只作为一种基本类型用作继承的类,称为抽象类(abstract class),由于它常用作基类,通常称为抽象基类。凡是包含纯虚函数的类都是抽象类。
如果在派生类中没有对所有的纯虚函数进行定义,则此派生类仍然是抽象类,不能用来定义对象。
可以定义指向抽象类数据的指针变量。当派生类成为具体类后,就可以用这个指针指向派生类对象,然后通过该指针调用虚函数。
带有纯虚函数的类称为抽象类。抽象类是一种特殊的类,它是为了抽象和设计的目的而建立的,它处于继承层次结构的较上层。抽象类是不能定义对象的,在实际中为了强调一个类是抽象类,可将该类的构造函数说明为保护的访问控制权限。抽象类的主要作用是将有关的组织在一个继承层次结构中,由它来为它们提供一个公共的根,相关的子类是从这个根派生出来的。抽象类刻画了一组子类的操作接口的通用语义,这些语义也传给子类。一般而言,抽象类只描述这组子类共同的操作接口,而完整的实现留给子类。抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。如果派生类没有重新定义纯虚函数,而派生类只是继承基类的纯虚函数,则这个派生类仍然还是一个抽象类。如果派生类中给出了基类纯虚函数的实现,则该派生类就不再是抽象类了,它是一个可以建立对象的具体类了。
函数的三种参数传递机制
在C++中,参数传递的方式是“实虚结合”。
按值传递(pass by value)
地址传递(pass by pointer)
引用传递(pass by reference)
按值传递的过程为:首先计算出实参表达式的值,接着给对应的形参变量分配一个存储空间,该空间的大小等于该形参类型的,然后把以求出的实参表达式的值一一存入到形参变量分配的存储空间中,成为形参变量的初值,供被调用函数执行时使用。这种传递是把实参表达式的值传送给对应的形参变量,故称这种传递方式为“按值传递”。使用这种方式,调用函数本省不对实参进行操作,也就是说,即使形参的值在函数中发生了变化,实参的值也完全不会受到影响,仍为调用前的值。
/*
pass By value
*/
#include
using namespace std;
void swap(int,int);
int main()
{
int a = 3, b = 4;
cout << "a = " << a << ", b = "
<< b << endl;
swap(a,b);
cout << "a = " << a << ", b = "
<< b << endl;
return 0;
}
void swap(int x, int y)
{
int t = x;
x = y;
y = t;
}
如果在函数定义时将形参说明成指针,对这样的函数进行调用时就需要指定地址值形式的实参。这时的参数传递方式就是地址传递方式。地址传递与按值传递的不同在于,它把实参的存储地址传送给对应的形参,从而使得形参指针和实参指针指向同一个地址。因此,被调用函数中对形参指针所指向的地址中内容的任何改变都会影响到实参。
[cpp] view plaincopy
#include
using namespace std;
void swap(int*,int*);
int main()
{
int a = 3, b = 4;
cout << "a = " << a << ", b = "
<< b << endl;
swap(&a,&b);
cout << "a = " << a << ", b = "
<< b << endl;
system("pause");
return 0;
}
void swap(int *x,int *y)
{
int t = *x;
*x = *y;
*y = t;
}
按值传递方式容易理解,但形参值的改变不能对实参产生影响。
地址传递方式虽然可以使得形参的改变对相应的实参有效,但如果在函数中反复利用指针进行间接访问,会使程序容易产生错误且难以阅读。
如果以引用为参数,则既可以使得对形参的任何操作都能改变相应的数据,又使得函数调用显得方便、自然。引用传递方式是在函数定义时在形参前面加上引用运算符“&”。
[cpp] view plaincopy
#include
using namespace std;
void swap(int&,int&);
int main()
{
int a = 3, b = 4;
cout << "a = " << a << ", b = "
<< b << endl;
swap(a,b);
cout << "a = " << a << ", b = "
<< b << endl;
system("pause");
return 0;
}
void swap(int &x,int &y)
{
int t = x;
x = y;
y = t;
}
综合
构造函数、拷贝构造函数、析构函数,调用顺序
构造函数、拷贝构造函数和析构函数的调用顺序
#include
class point
{
private:
int x,y;
public:
point(int xx=0,int yy=0)
//1.构造函数不能有返回值
//2.缺省构造函数时,系统将自动调用该缺省构造函数初始化对象,缺省构造函数会将所有数据成员都
//初始化为零或空
// 3.创建一个对象时,系统自动调用构造函数
{
x=xx;
y=yy;
cout<<"构造函数被调用"< } point(point &p); ~point(){cout<<"析构函数被调用"< //析构函数没有参数,也没有返回值。不能重载,也就是说,一个类中只可能定义一个析构函数 //如果一个类中没有定义析构函数,系统也会自动生成一个默认的析构函数,为空函数,什么都不做 //调用条件:1.在函数体内定义的对象,当函数执行结束时,该对象所在类的析构函数会被自动调用; // 2.用new运算符动态构建的对象,在使用delete运算符释放它时。 int get_x(){return x;} int get_y(){return y;} }; point::point(point &p)//拷贝构造函数实际上也是构造函数,具有一般构造函数的所有特性,其名字也 //与所属类名相同。拷贝构造函数中只有一个参数,这个参数是对某个同类对象的引用。//在三种情况下被调用:1.用类的一个对象去初始化该类的另一个对象时; // 2.函数的形参是类的对象,调用函数进行形参和实参的结合时;这也是为什么 // 拷贝构造函数的参数需要加引用,避免反复循环调用构造函数。 // 3.函数的返回值是类的对象,函数执行完返回调用者。 { x=p.x; y=p.y; cout<<"拷贝构造函数被调用"< } void f(point p) { cout< } point g() { point a(7,33); return a; } void main() { point a(15,22); point b(a); cout< f(b); b=g(); cout< } 程序运行结果: 构造函数被调用 //point a(15,22); 拷贝构造函数被调用//point b(a);拷贝构造函数的第一种调用情况 15 22 拷贝构造函数被调用//f(b);拷贝构造函数的第二种调用情况 15 22 析构函数被调用//f(b);析构函数的第一种调用情况 构造函数被调用//b=g();的函数体内point a(7,33);创建对象a 拷贝构造函数被调用//b=g();拷贝构造函数的第三种调用情况,拷贝a的值赋给b。 析构函数被调用//拷贝构造函数对应的析构函数 析构函数被调用//b=g();的函数体内对象a析构 7 33 析构函数被调用//主函数体b对象的析构 析构函数被调用//主函数体a对象的析构 由此看出:析构函数的顺序与构造函数的顺序刚好相反。 虚函数、纯虚函数,如何使用 1.虚函数和纯虚函数可以定义在同一个类(class)中,含有纯虚函数的类被称为抽象类(abstract class),而只含有虚函数的类(class)不能被称为抽象类(abstract class)。 2.虚函数可以被直接使用,也可以被子类(sub class)重载以后以多态的形式调用,而纯虚函数必须在子类(sub class)中实现该函数才可以使用,因为纯虚函数在基类(base class)只有声明而没有定义。 3.虚函数和纯虚函数都可以在子类(sub class)中被重载,以多态的形式被调用。 4.虚函数和纯虚函数通常存在于抽象基类(abstract base class -ABC)之中,被继承的子类重载,目的是提供一个统一的接口。 5.虚函数的定义形式:virtual {method body} 纯虚函数的定义形式:virtual { } = 0; 在虚函数和纯虚函数的定义中不能有static标识符,原因很简单,被static修饰的函数在编译时候要求前期bind,然而虚函数却是动态绑定(run-time bind),而且被两者修饰的函数生命周期(life recycle)也不一样。 6.如果一个类中含有纯虚函数,那么任何试图对该类进行实例化的语句都将导致错误的产生,因为抽象基类(ABC)是不能被直接调用的。必须被子类继承重载以后,根据要求调用其子类的方法。 以下为一个简单的虚函数和纯虚寒数的使用演示,目的是抛砖引玉! #include <> //father class class Virtualbase { public: virtual void Demon()= 0; //prue virtual function virtual void Base() {cout<<"this is farther class"< } //sub class class SubVirtual :public Virtualbase { public: void Demon() { cout<<" this is SubVirtual!"< void Base() { cout<<"this is subclass Base"<}; } /* instance class and sample */ int main() { Virtualbase* inst = new SubVirtual(); //multstate pointer inst->Demon(); inst->Base(); } 虚函数和纯虚函数区别 观点一: 类里声明为虚函数的话,这个函数是实现的,哪怕是空实现,它的作用就是为了能让这个函数在它的子类里面可以被重载,这样的话,这样编译器就可以使用后期绑定来达到多态了纯虚函数只是一个接口,是个函数的声明而已,它要留到子类里去实现。 class A{ protected: void foo();//普通类函数 virtual void foo1();//虚函数 virtual void foo2() = 0;//纯虚函数 } 观点二: 虚函数在子类里面也可以不重载的;但纯虚必须在子类去实现,这就像Java的接口一样。通常我们把很多函数加上virtual,是一个好的习惯,虽然牺牲了一些性能,但是增加了面向对象的多态性,因为你很难预料到父类里面的这个函数不在子类里面不去修改它的实现 观点三: 虚函数的类用于“实作继承”,继承接口的同时也继承了父类的实现。当然我们也可以完成自己的实现。纯虚函数的类用于“介面继承”,主要用于通信协议方面。关注的是接口的统一性,实现由子类完成。一般来说,介面类中只有纯虚函数的。 观点四: 带纯虚函数的类叫虚基类,这种基类不能直接生成对象,(否则会出现错误!)而只有被继承,并重写其虚函数后,才能使用。这样的类也叫抽象类。虚函数是为了继承接口和默认行为纯虚函数只是继承接口,行为必须重新定义。 派生关系 1. 派生类对象作为基类对象处理 由于派生类具有所有基类的成员,所以把派生类的对赋给基类对象是合理的,不过要求这种继承方式必须是public方式。但是,反过来赋值会使基类中一具有派生类的成员(因为派生类的成员通常是比基类的成员多),所以这是不允许的 2. 基类指针指向派生类对象 因为派生类对象也是基类对象,所以指向派生类对象的指针可以转换为指向基类对象的指针,这种引用方式是安全的,但是用这种方式只能引用基类成员。如果试图通过基类指针引用那些只有在派生类中才有的成员,编译系统会报告错误。 3. 派生类指针强制指向基类对象 直接用派生类指针指向基类的对象,这种方式会导致语法错误。但可以将派生类强制转换为基类指针,然后就可以调用基类的成员了。这种强制转换使用的静态转型运算符,其使用格式如下: 派生类对象指针=static_static<派生类*>(&基类对象); 运算符优先顺序、组合 遍历了15个级别之后,让我们再来总结一下。其中我们可以看出这样两个规律: 规律一、按照操作数个数来区分,一元运算符高于二元运算符,二元运算符高于三元运算符; 规律二、按照运算符的作用来区分,级别最高的是那些不是严格意义上的运算符,次之是算术运算符,位移运算符,关系运算符,位运算符,逻辑运算符,赋值运算符。 此外还有两特别的地方需要注意: 一、同为关系运算符,但==和!=的级别低于其它四个; 二、第2组与第13组的操作符是右结合的,其它的都为左结合; 通过分类我们大大减少了需要记忆的内容,遇到使用操作符的时候,我们只需想想它们是什么类型的运算符就可以确定它们之间的相对优先级,从而避免一些不必要的错误。 编程 进制转换:2、8、16进制字符串与十进制整形数之间的转换 十进制转二进制: [cpp] view plaincopyprint? //十进制转二进制 #include using namespace std; void printbinary(const unsigned int val) { for(int i = 16; i >= 0; i--) { if(val & (1 << i)) cout << "1"; else cout << "0"; } } int main() { printbinary(1024); return 0; } 十进制转八进制 [cpp] view plaincopyprint? //十进制转八进制 #include #include using namespace std; int main() { cout<<"input a number:"< int d; vector cin>>d; while (d) { vec.push_back(d%8); d=d/8; } cout<<"the result is:"< for(vector cout<<*ip--; } cout< return 0; } 十进制转任意进制: [cpp] view plaincopyprint? //十进制转换为任意进制的源码 #include using namespace std; int main() { long n; int p,c,m=0,s[100]; cout<<"输入要转换的数字:"< cin>>n; cout<<"输入要转换的进制:"< cin>>p; cout<<"("< while (n!=0)//数制转换,结果存入数组s[m] { c=n%p; n=n/p; m++;s[m]=c; //将余数按顺序存入数组s[m]中 } for(int k=m;k>=1;k--)//输出转换后的序列 { if(s[k]>=10) //若为十六进制等则输出相对应的字母 cout<<(char)(s[k]+55); else //否则直接输出数字 cout< } cout<<")"< return 0; } 通过库函数实现八进制、十六进制输出: [cpp] view plaincopyprint? #include using namespace std; int main() { int test=64; cout<<"DEC:"< cout<<"OCT:"< } [ 排序方法,冒泡…… 一维 #include using namespace std; int main() { int a[10],i,j,t; cout<<"请输入10个整数:"; for(i=0;i<10;i++) {cin>>a[i];} for(i=0;i<10;i++) for(j=i;j<10;j++) if(a[i]>a[j]) { t=a[i];a[i]=a[j];a[j]=t; } cout<<"结果为:"; for(i=0;i<10;i++) {if(a[i]!=a[i-1])