C++进阶

一.仿函数

//仿函数——对象可以像函数一样使用
template<class T>
struct Less
{
	bool operator()(const T& x, const T& y)
	{
		return x < y;
	}
};

int main()
{
	Less<int> less; //定义一个Less对象
	cout << less(1, 2) << endl;   //这里的less是一个对象,但是可以像函数一样使用
	cout << sizeof(less);       //而且大小为1
	return 0;
}

二.反向迭代器

end,rend,begin,rebegin是对称的

namespace mst
{
	template<class Iterator,class Ref,class Ptr>
	struct ReverseIterator
	{
		typedef ReverseIterator<Iterator,Ref,Ptr> Self;
		Iterator _cur;

		ReverseIterator(Iterator it)     //用正向迭代器构造反向迭代器
			:_cur(it)
		{}

		Self& operator++()
		{
			--_cur;
			return *this;
		}

		Self& operator--()
		{
			++_cur;
			return *this;
		}

		bool operator!=(const Self& s)
		{
			return _cur != s._cur;
		}

		Ref operator*()
		{
			Iterator tmp = _cur;   //返回前一个位置再解引用,就比如_cur=_head的时候
			--tmp;
			return *tmp;
		}

	};


	template<class T>   //用list来举列子
	class list
	{
		typedef ReverseIterator<iterator, T&, T*> reverse_iterator;
		typedef ReverseIterator<iterator, const T&, const T*> const_reverse_iterator;

		reverse_iterator rbegin()
		{
			return reverse_iterator(end());
		}

		reverse_iterator rend()
		{
			return reverse_iterator(begin());
		}

		//list的实现...
	};
}

三.模板进阶

1.非类型模板参数

非类型模板参数——整形常量(可缺省值,而且类型只能是整型常量,char也算整形,bool类型也可以)

template<class T, size_t N = 20 >
class Array
{
public:


private:
	T _a[N];
};

int main()
{
	Array<int, 10> a1;
	Array<double, 20> a2;

	return 0;
}

之前的都是类型模板参数,用T来表示int,double,string之类的

2.模板特化——不能只有特化

①全特化

//普通模板
template<class T>
bool Less(T left, T right)
{
	return left < right;
}

//模板特化——对某些类型进行特殊化处理

//函数模板特化写法  (其实不如直接写函数重载)
template<>  
bool Less<Date*>(Date* left, Date* right)   //这时候当T是Date*的时候走这条路,其他都走上面那条路
{
	return *left < *right;
}

//函数重载
bool Less(Date* left, Date* right)   
{
	return *left < *right;
}


//类模板特化写法
struct less<Date*>
{
	bool operator()(const Date* x, const Date* y)
	{
		return *x < *y;
	}
};

②偏特化

//偏特化——扩大了范围
template<class T>
struct less<T*>     //只要是指针,都走这条路,用指针指向的对象去比较
{
	bool operator()(const T* l, const T* r) const
	{
		return *l < *r;
	}
};

③半特化——对部分参数特化

template<class T>
class Date<T, int>    //只有第二个是int就走这条线
{
	date()
	{
		;
	}
	T _d1;
	int _d2;
};

      特化优先走最匹配的

3.模板的分离编译

①预处理,编译,汇编和链接

②分离编译

  普通函数会被编译成一堆指令,所以在func.o中有fnc函数的地址,但是没有Add的地址,因为Add还没有实例化,没法确定T,不知道开多大。

先声明了,但是在链接的时候找不到了(因为没有实例化),call不到add函数

所以:模板不支持分离编译会报链接错误,最好解决方案就是在同一个文件里声明和定义,直接就可以实例化,编译时直接就有地址,不需要链接。

四.继承

1.基础知识(父类——共有的对象)

2.访问限定符/访问权限

    private和protected 在当前类没有什么区别,但是继承的时候,private在子类(派生类)完全不可见(不能用),但是可以用公共的函数来使用,protected在子类是可见的。

3.赋值类型转换

//父类对象不能赋值给派生类对象,而派生类对象能赋值给父类对象
int main()
{
	student s;
	Person p = s;  //发生了隐式类型转换
	return 0;
}

引用,指针,都是取出了子类的切片(一部分) 

4.隐藏/重定义

   子类和父类中有同名成员,子类成员将屏蔽父类对同名成员的直接访问,这种情况就叫隐藏(重定义)。只要函数名相同就构成隐藏。

    与函数重载的最大区别:函数重载的两个函数必须在同一作用域里。

class Person
{
protected:
	string _name = "mst";
	int _age = 18;
};

class student :public Person
{
public:
	void print()
	{
		cout << "name: " << _name;
		cout << endl;
		cout << "age: " << _age;   //作用域是子类(派生类)优先
        out << "age: " << Person::_age;  //加上域作用限定符就可以访问父类的了
	}
protected:
	int _age = 22;
};


int main()
{
	student s;
	s.print();
	return 0;
}

5.派生类中的默认成员

对于这六大函数,若派生类不写,会直接用父类里的 

class Person
{
public:
	Person(const char* name = "mst")
		:_name(name)
	{
		cout << "调用了父类的构造函数" << endl;
	}

	Person(const Person& p)
		:_name(p._name)
	{
		cout << "调用了父类的拷贝构造" << endl;
	}

	Person& operator=(const Person& p)
	{
		cout << "调用了父类的==构造" << endl;
		if (this != &p)
		{
			_name = p._name;
		}
		return *this;
	}
	
	~Person()
	{
		cout << "调用了父类的析构函数" << endl;
	}
protected:
	string _name = "mst";
	int _age = 18;
};



class Student :public Person
{
public:
	Student(const char* name, int num)
		:Person(name),_num(num)    //如果用子类自己的函数:父类初始化父类的值(用了构造函数),子类初始化子类的值,两者分开执行
	{}

	Student& operator=(const Student& s)
	{
		if (this != &s)
		{
			Person::operator=(s);    //注意这里构成隐藏,要写成“Person::”才会调用父类的拷贝构造
			_num = s._num;   //如果用子类自己的拷贝构造:父类拷贝父类的,子类处理子类的 ,两者互相分开工作,
		}
		return *this;
	}

	~Student()
	{
		//Person::~Person();
		cout << "~Student()" << endl;
	}
 /子类析构函数完成时,会自动调用父类析构函数,保证先析构子再析构父(构造时先构造父,再构造子)
protected:
	int _num;
};

int main()
{
	Student s("张三",18);
	Student s2(s);   //调用了父类的默认拷贝构造
	Student s3 = s;
	Person p = s;
	s2 = s3;
	return 0;
}

1.不写六大函数时,子类会自动调用父类的构造析构函数 
2.在子类中初始化父类变量时,必须调父类的构造函数 ,不管写不写,只要是父类的,都得调用父类的构造函数来初始化

6.注意

①友元关系不会继承。

②静态变量属于整个类,不论子类还是父类都使用那同一个静态变量。

③不能被继承的类

:父类的构造/析构函数是私有。

7.多继承

菱形继承导致的问题:数据冗余和二义性

解决方法:用虚拟继承

class Person
{
public:
	string _name;
};

class Student : virtual public Person  //虚继承
{
protected:
	int _num;
};

class Teacher : virtual public Person
{
protected: 
	int _id;
};

class Assistant : public Student, public Teacher
{
protected:
	string _majorCourse;
};

void test()
{
	Assistant a;
	a._name = "zhangsan";
	a.Student::_name = "xx";    //用了虚继承,不论是a.Student::_name还是a.Teacher::_name,都指向了同一个_name
	a.Teacher::_name = "yyy";   
}

int main()
{
	test();
	return 0;
}

   只有一份a,所以放到了公共位置,通过偏移量来找a。这里b和c存的地址指向的是其对应的偏移量。无论有多少个d变量(d1,d2...),所有的d变量的指针都是一样的(0x00aacd8c),都对应着相同的偏移量。

eg:

面试题:

        初始化列表跟顺序无关,只跟声明的顺序有关,谁先声明,谁就先初始化。谁先被继承,谁就先被声明,因为D最后声明,所以D肯定在最后。

        先执行A(s1),在执行B(),然后C(),然后D 。 因为A先被声明,D中B先被继承,C后被继承。

多继承的指针偏移:

P2会自己指到自己的区域

 

8.继承与组合 

//公有继承 
class A
{};

class B : public A
{};


//组合——组合也是一种复用  适配器就是一种组合
class C
{};

class D
{
private:
	C _cc;
};

耦合度(项目之间的相互影响)越低越好

五.多态 ——某个行为针对不同对象有多种形态

1.多态的条件

①虚函数的重写——三同(函数名,参数,返回值)

特例1:当父类带virtual,子类不带virtual时,构成接口继承——接口继承: 接口(函数名)继承,函数体重写,所以可以不加virtual,注意,其中缺省的参数也继承了。

注意: 多态中父类和子类的析构函数也要加virtual

// 虚函数 把virtual加到一个函数的前面构成虚函数

class Person  
{
public:
	virtual void buy(int x) { cout << "成人票" << endl; }
};

class Student : public Person
{
public:
	//如果加了virtual,而且函数名,参数,返回值都相同,那么会构成重写/覆盖 ,不加virtual就是隐藏
	virtual void buy(int y) { cout << "学生票" << endl; }
};

void func(Person& p)
{
	/不满足多态——看调用者的类型,调用这个类型的成员函数 (当p.buy()时,调用的都是person的,当s.buy()时就相反)
	/满足多态——看父类指针指向的对象类型,调用这个被指向的类型的成员函数(指向(引用)父类调父类,指向子类调子类)
	p.buy();
}

int main()
{
	Person p;
	Student s;
	func(p);
	func(s);
	return 0;
}

特例2:返回值可以不同,但必须是父子关系的指针或者引用,构成协变


class Person
{
public:
	virtual Person* buy() 
	{ 
		cout << "成人票" << endl; 
		return this;
	}
};

class Student : public Person
{
public:
	//如果加了virtual,而且函数名,参数,返回值都相同,那么会构成重写/覆盖 ,不加virtual就是隐藏
	virtual Student* buy() 
	{ 
		cout << "学生票" << endl; 
		return this;
	}
};

②用父类的指针或者引用去调用(传一个父类指针或引用过来) 必须是父类!!!

面试题:

这里的A* this 是隐藏的父类指针。

2.final和override关键字

①final:加载虚函数后面,表示该虚函数不能再被重写

class Person  
{
public:
	virtual void buy() final { cout << "成人票" << endl; }
};

override:检查是否完成重写,没有完成就报错,完成了重写就无事发生

class Student : public Person
{
public:
	virtual void buy() override { cout << "学生票" << endl; }
};

3.函数重载, 函数重写和函数重定义

4.抽象类 


class Car
{
public:
	//纯虚函数——>抽象类——>不能实例化出对象
	virtual void Drive() = 0;  //这是一个纯虚函数
};

5. 多态的原理

1.引入问题

Base的实际内存分布如下: 

这里实际上隐藏了一个虚函数表指针(占4个字节)

2.虚函数表

 注意:

1.跟虚拟继承一样,person1,person2,person3...同类的对象共用一个虚函数表(里面放着虚函数)。
2.虚函数表:本质是一个函数指针数组(虚函数指针数组),虚函数表的指针(虚表指针)指向它

3.子类虚函数重写时:先把父类的虚函数表拷贝过来,重写的虚函数才覆盖,不重写的不覆盖

3.为什么只能是父类的指针或引用

原因:子类对象切片拷贝时(赋值时)不会拷贝虚表,只拷贝成员  。

4.打印虚表的函数


//实际上应该是这个样: typedef void(* )() VF_PTR;
typedef void(*VF_PTR)();  //函数指针

void PrintVFTable(VF_PTR table[])
{
	for (int i = 0; table[i] != nullptr; i++)  //只有vs环境下,虚表的最后一个才是nullptr(vs的改进)
	{
		printf("[%d]:%p\n", i, table[i]);
	}
	cout << endl;
}

int main()
{
	Base b;
	Derive d;

	PrintVFTable((VF_PTR*)*(int*)&b);  //只适用32位,64位用long long 
	//为了只去虚表的第一个指针(头四个字节),所以把b的地址转换成int*类型,之后在转回VF_PTR*(函数指针数组类型)
	//如果是PrintVFTable(&b),那么解引用就是一整个虚表 
	PrintVFTable((VF_PTR*)*(int*)&d);

	//PrintVFTable((VF_PTR*)&d);    结果是传不过去
	PrintVFTable(*(VF_PTR**)&d);   //这样也可以   先强转类型再接引用
	return 0;
}

 5.其他细节

   1.虚表是什么阶段生成的——编译
   2.对象中虚表指针是在什么时候初始化的——构造函数的初始化列表时
   3.虚表存在哪里——代码段(常量区)

   4. 多继承中派生类中新增加的虚函数放在第一张(先继承的那个)的虚表。

5.静态多态:编译时  一般是函数重载,根据传的参数去进行匹配
   动态多态:运行时  运行起来之后去虚表里面去找(指针指向谁,调用谁)

 

总结题:

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

对玛导至昏

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值