考前看一下
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{
public:
virtual void func1();
virtual void func2();
virtual void func3();
void func4();
};
class Derived:public Base {
public:
virtual 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{
public:
Base(int x,int y)
{
a=x;
b=y;
}
void show()
{
cout<<"Base----------\n";
cout<<a<<" "<<b<<endl;
}
private:
int a,b;
};
class Derived :public Base
{
public:
Derived(int x,int y,int z):Base(x,y)
{ c=z; }
void show()
{
cout<< "Derived---------\n";
cout<<"c="<<c<<endl;
}
private:
int 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函数外面赋值。