OOP刷题

考前看一下

1.通过指针调用成员函数,首先看指针类型是否有这个成员函数

如果有,是否是virtual,如果是,则看对象类型。

如果没有,那么看基类之类的

2.=和拷贝构造函数,如果A a=c是拷贝构造, A a; a = c; 是等号

3.const, 只能用const成员函数访问

4.顺序:同级可能在模板,特化,函数中找。顺序是函数>特化>模板

不同级:精确(指针和数组的转化) > 加减const > 类型提升 > 算术转换 > 类类型转换

如果模板,特化,函数匹配到任意一级,直接就输出了,如果没有匹配到,才往下一级

5.在构造函数中,不仅看是否继承了别的类,而且看有类作为成员变量。如果有,调用顺序是基类构造函数->成员变量构造函数->子类构造函数

6.父类和子类同名变量不能覆盖。如果子类调用父类的函数,那么父类函数只能访问父类的成员变量,而不能访问子类的成员变量。

1 类和对象

1.调用没有参数的构造函数不能加括号,比如A a是正确的,A a()是不正确的

2.拷贝构造函数调用的三种情况:

这是等价的:A a = c; A a(c);

函数返回类型A的参数时,函数有类型为A的参数时

如果A构造函数只有一个参数,那么A a = 'c’等价于 A a(‘c’)

通过在构造函数前面加上explicit取消这种方式。

3.如果函数参数表里有const,那么传入一个const类型或者非const类型都可以。如果函数参数表里没有const,那么只能传入非const类型,传入const会错误。

4.常对象只能调用常函数或者静态函数,不能使用普通成员函数

例题1:

#include <iostream>
struct A {
	A() { std::cout << "A" << std::endl; }
	A(const A& a) { std::cout << "B" << std::endl; }
	A& operator=(const A& a) { std::cout << "C" << std::endl; return *this; }
};
int main() {
	A a[2];
	A b = a[0];
	A c;
	c = a[1];
}

第一个是对象数组,初始化两次

第二个是拷贝,调用拷贝构造函数一次

第三个是初始化,第四个是重载等号

例题2:

#include <iostream>
using namespace std;
class A{
public:
	static void f(double) {
		cout << "f(double)" << endl;
	}
	void f(int){
		cout << "f(int)" << endl;
	}
};
int main(){
	const A a;
	a.f(3);
}

常对象不能使用普通成员函数,所以选择了静态函数,输出double

常对象只能使用被const修饰的成员函数或者是静态函数。

2 继承

继承其实是派生类在父类的基础上增加了一些成员函数和变量。因此,拷贝的时候肯定会调用父类的拷贝函数。

1.首先调用基类构造函数,再调用派生类

2.析构函数的顺序正好是相反的。

3.如果遇到虚继承,需要显式调用基类构造函数。但是基类构造函数只会在第一次遇到子类时调用。比如E():A(), B(), C(), D()

其中B,C虚继承了D, 那么调用顺序是A, D, B, C。 从左到右调用构造函数。遇到了虚继承的子类,如果之前没有调用基类,那么首先调用基类。

class A {
public:
	A() { cout << "A()" << endl; }
	~A() { cout << "~A()" << endl; }
};

class B :public A {
public:
	B() { cout << "B()" << endl; }
	~B() { cout << "~B()" << endl; }
};

int main() {
	A a;
	B b;
}

结果:
A()
A()
B()
~B()
~A()
~A()

3 多态性

1.重载和虚函数区别:父类指针指向子类对象,如果子类重载了父类虚函数,那么可以用父类指针调用子类函数。如果是重载,只能调用父类函数。

因此如果子类想要重载父类的函数,最好将父类函数定义为虚函数

2.纯虚函数是父类中不实现虚函数,而完全由派生类实现。有纯虚函数的类称为抽象类,不能建立对象,不能作为参数类型,不能作为返回类型,可以声明指针或者引用。

3.默认值永远看指针声明时指向的类。不会因为指针指向了子类,调用函数时使用子类的默认值替换基类的默认值。

4.static_cast<> 可以上行下行转化,不会有NULL

dynamic_cast<> 可以进行上行下行转化,但是可能出现NULL

如果基类指针转成派生类指针,会检查基类指针指向对象是不是真的是派生类。如果不是,则变成NULL

const_cast<> 把一个const对象取消const

例题1:

	#include<iostream.h>
	class Base{
	publicvirtual void func1(); 
	    virtual void func2();  
         virtual void func3();  
	    void func4();          
	};
	class Derivedpublic Base {
	publicvirtual void func1();
         void func2(int x); 
         char func3();
         void func4();
	};
Base d1,*bp;
	    Derived d2;
	    bp=&d2;
	    bp->func1();     	//调用Derived∷func1()
	    bp->func2();     	//调用Base∷func2()
	    bp->func4();     	//调用Base∷func4()

func1是虚函数重载,因此调用子类

func2子类没有重载虚函数,因此直接调用父类

fun4不是虚函数,因此调用父类

例题2:

#include<iostream.h>
	class Base{
	publicBase(int x,int y)
	    {   
        a=x;  
        b=y; 
      }
	    void show()
	    {
	        cout<<"Base----------\n";
	        cout<<a<<" "<<b<<endl;
	    }
	privateint a,b;
};
class Derivedpublic Base
	{
	publicDerived(int x,int y,int z)Base(x,y)
	    {   c=z;   }
	    void show()
	    {
	        cout<< "Derived---------\n";
	        cout<<"c="<<c<<endl;
	    }
	    privateint c;
       };
	void main()
	{
	    Base mb(60,60),*pc;
	    Derived mc(10,20,30);
	    pc=&mb;
	    pc->show();
	    pc=&mc;
	    pc->show();
      }

pc调用的仍然是基类的show,原因是静态联编,使得指针pc和基类的show绑定在一起。此时如果把基类的show定义为虚函数,则可以正常实现调用。

例题4:

如果基类函数A不是虚函数,子类重载函数A,基类指针p指向子类,调用函数A,只会对子类基类的成员变量进行访问。

如果把A定义为虚函数,则正常对子类所有变量访问。

例题5:

#include <iostream>

struct A {
	virtual void foo(int a = 1) {
	std::cout << "A" << "\n" << a;
	}
};
struct B: A {
	virtual void foo(int a = 2) {
	std::cout << "B" << "\n" << a;
	}
};
int main() {
	A* a = new B;
	a->foo();
}
	

B
1.

由于是虚函数,所以指针指向了子类,调用了子类的函数。但是默认值看指针声明时指向的对象,因此使用了基类的默认值。

4 运算符重载

1.正常重载

返回类型 operator 运算符(参数) {函数体};

如果是双元运算,第一个参数是类本身,第二个参数是括号里的。

如果是单元运算,参数填0说明是后置运算,不填是前置运算

注意,如果重载+=这类,必须返回自身的引用。

2.友元函数重载

也可以声明为友元函数,这个跟静态的差不多。

friend 返回类型 operator 运算符(参数1, 参数2){函数体};

类型转换:当出现两个类型运算时,系统首先找有没有重载运算,如果没有,寻找有没有类型转换。

3.类型强制转化

operator 类型(){函数体}; 将该类强制转化为指定类型。

例题1

#include<cstring>
#include<iostream>
using namespace std;

class Str {
	char m_s[10];
	char* m_p;
public:
	Str(char* s) { strcpy(m_s, s); m_p = s; }
	operator char* () { return m_p; }
	char* operator++() { return ++m_p; }
	char operator [](int i) { return m_s[i]; }
};

int main() {
	Str s((char*)"Hi");
	cout << *s << endl;
	++s;
	cout << s[0] << endl;
	cout << *s << endl;
}

operator char*() 就是重载了char*(s),将s转为char*类型的数值返回。

首先,对于*运算符,对象一定是指针类型的,于是看一下s是否可以转为指针类型。发现可以转成char* 类型,于是就转了。返回了m_p,然后把m_p指向的对象直接弄掉。操了

s[0],是m_s第0个,还是’H’(这里要小心)

*s,是*m_p,是’i’

5 输入输出

考个寄吧

6 模板

1.模板全特化:定义函数模板以后,如果要特定的类型不走模板,可以对模板进行特化,如果调用函数的参数和模板特化参数相同,那么调用的是特化的函数

例题1:

#include <iostream>
using namespace std;
template<typename T>
T func(T x, double y){
	return x+y;}
int main(){
	cout << func(2.7, 3)<< endl;
	cout << func(3, 2.7)<< endl;
}

5.7
5
首先,3被转为了double,T被替换为double,返回了double

然后T被替换为int,计算值5.7被转为5

7 STL和类库

考毛线

8 异常处理

这tm还考

异常处理过程:

1.发生异常,首先拷贝一个异常对象

2.寻找catch, 如果未找到转到上一级寻找catch,如果在main中未找到,直接abort

3.如果找到了,对所有try开始到异常点的对象进行析构(异常对象本身也被析构)

4.执行catch内中的语句

5.在catch语句中,可以使用throw; 把原来的对象再次抛出。此时不是拷贝。

类型转换

例题2:

#include <iostream>
using namespace std;
class A {
public:
	void F(int) { cout << "A: F(int)" << endl; }
	void F(double) { cout << "A: F(double)" << endl; }
	void F2(int) { cout << "A: F2(int)" << endl; }
};

class B : public A{
public:
	void F(double) {
		cout << "B: F(double)" << endl;
	}
};

int main(){
	B b;
	b.F(2.0);
	b.F(2);
	b.F2(2);
}

B: F(double)
B: F(double)
A: F2(int)

原因是隐式类型转换先于类类型转换

例题3

#include <iostream>
template<class T> void f(T i){std::cout << 1;}
template<> void f(const int i){std::cout << 2;}
int main() {
	int i = 24;
	f(i);
}

在第一级匹配到普通函数,输出2

#include <iostream>
template<class T> void f(T& i){std::cout << 1;}
template<> void f(const int& i){std::cout << 2;}
int main() {
	int i = 24;
	f(i);
}

第一级匹配到了模板函数,输出1

#include <iostream>
template<class T> void f(T& i){std::cout << 1;}
template<> void f(const int& i){std::cout << 2;}
int main() {
	const int i = 24;
	f(i);
}

第一级匹配到了普通函数,输出2

编程

特点:本身不是很难,但是会考c++本身的一些内容,比如模板和异常和常量之类的

1.模板:template<class T>

成员函数定义时候必须加上类参数:

template<class T>
void CC<T> fun(...){
	//函数初始化
}

2.常函数:声明的时候是int fun(){} const;

定义的时候int fun() const{}

3.常成员变量初始化只能使用参数表

CC(int a): b(a){
	//构造函数
}

4.类数组,使用的是new class[t]; 不是new class(t),这个是初始化

5.异常就是throw e; 或者throw error(“error”); 调用异常类的构造函数。

异常类的构造函数一般需要一个字符串初始化。

6.注意构造函数不返回任何值,如果需要赋值给一个对象,只能用new

重点

1.几个顺序

构造函数顺序:

基类构造函数->派生类内嵌对象构造函数,按照声明顺序->派生类构造函数体内容。

析构函数相反

函数重载顺序:

1.精确匹配,包括实参类型和形参类型相同,实参从数组或函数转换成对应的指针类型,向实参添加顶层const或从实参删除顶层const

2.通过const转换实现的匹配

3.通过类型提升实现的匹配(short和int, double和float)

4.通过算数类型转换实现的匹配(int 和float之类)

5.通过类类型转换实现的匹配(子类父类)

如果模板,普通函数,特化函数在同一级,顺序是普通函数>特化函数>模板

6.小心对于子类重新定义的成员变量,和基类的变量同名,但是不会被覆盖。也就是说一个类里面存在两个变量。

#include<iostream>
class A {
	int i;
public :
	A() :i(0) { }
	virtual void set(int i) { this->i = i; }
	virtual int get() { return this->i; }
};

class B :public A {
	int i;
public:
	B() : i(10) {}
	virtual void set(int i) { this->i = i; }
};

int main() {
	B b;
	A* p = &b;
	std::cout << p->get();
}

由于子类重新定义了i,于是修改和初始化的都是子类的i,基类的i还是0

所以输出0

7.小心虚构函数如果是父类指针,而析构函数不是虚函数,那么不会调用子类的析构函数。

#include<iostream>
using namespace std;
class MyClass {
public:
	MyClass(int id) { cout << "MyClass::Ctor\n"; }
	~MyClass() { cout << "MyClass::Dtor\n"; }
	int id;
};

class Base {
public:
	Base(int id):myClass(id) { cout << "Base::Ctor\n"; }
	~Base() { cout << "Base::Dtor\n"; }
	MyClass myClass;
	virtual void foo() { cout << "Base::foo\n"; }
};

class Derived:public Base {
public:
	Derived(int id) :Base(id) { cout << "Derived::Ctor\n"; foo(); }
	~Derived() { cout << "Derived::Dtor\n"; }
	virtual void foo() { cout << "Derived::foo\n"; }
};

int main() {
	Base* p = new Derived(10);
	delete p;
	return 0;
}	

输出的是

MyClass::Ctor
Base::Ctor
Derived::Ctor
Derived::foo
Base::Dtor
MyClass::Dtor

8.引用就是相当于别名。引用只允许在初始化的时候赋值,之后的赋值看作调用拷贝,但是内存不变。因此遇到引用,全部换成赋值时的变量名称。

9.遇到问继承的,先画出继承关系。然后看指针的类型,如果指针类型当前函数是虚函数,那么看被指向的对象类型。

10.静态变量就是全局变量。析构函数也是在main里面调用。全局变量中析构函数调用的顺序是按照构造函数的顺序倒过来。

11.在函数中的声明的静态变量,内存在开始时就分配好了。如果多次调用这个函数,那么结果是重新赋值,而不是产生多个静态变量

类中的静态变量,不能在任何函数内赋值,不能在.h里面赋值,.h因为可能被调用多次。

只能在main函数外面赋值。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值