纯虚函数是一种特殊的虚函数,基类定义后(~=0)必须由派生类重写,纯虚函数将父类上升为一个抽象类,无法实例化对象;抽象类是指具有纯虚函数的类;一个基类说明有纯虚函数,该基类的派生类可以是抽象类;抽象类只能作为基类来使用,其纯虚函数的实现由派生类给出。
一、纯虚函数定义.
是在基类中声明的虚函数,它在基类中没有定义,但要求任何派生类都要定义自己的实现方法。在基类中实现纯虚函数的方法是在函数原型后加“=0”
二、引入原因:
1、为了方便使用多态特性,我们常常需要在基类中定义虚拟函数。
2、在很多情况下,基类本身生成对象是不合情理的。例如,动物作为一个基类可以派生出老虎、孔雀等子类,但动物本身生成对象明显不合常理;为了解决上述问题,引入了纯虚函数的概念,将函数定义为纯虚函数(方法:virtual ReturnType Function() = 0;),则编译器要求在派生类中必须予以重载以实现多态性。同时含有纯虚拟函数的类称为抽象类,它不能生成对象。这样就很好地解决了上述两个问题。
三、相似概念:
i、多态性
指相同对象收到不同消息或不同对象收到相同消息时产生不同的实现动作。C++支持两种多态性:编译时多态性,运行时多态性。a.编译时多态性:通过函数重载和运算符重载来实现的;b.运行时多态性:通过继承和虚函数来实现的。
ii、虚函数
虚函数是在基类中被声明为virtual,并在派生类中重新定义的成员函数,可实现成员函数的动态重载,纯虚函数的声明有着特殊的语法格式:virtual 返回值类型 成员函数名(参数列表)=0;
请注意,纯虚函数应该只有声明,没有具体的定义,即使给出了纯虚函数的定义也会被编译器忽略。
iii、抽象类
包含纯虚函数的类称为抽象类。由于抽象类包含了没有定义的纯虚函数,所以不能定义抽象类的对象。
在C++中,我们可以把只能用于被继承而不能直接创建对象的类设置为抽象类(Abstract Class)。
之所以要存在抽象类,最主要是因为它具有不确定因素。我们把那些类中的确存在,但是在父类中无法确定具体实现的成员函数称为纯虚函数。纯虚函数是一种特殊的虚函数,它只有声明,没有具体的定义。抽象类中至少存在一个纯虚函数;存在纯虚函数的类一定是抽象类。存在纯虚函数是成为抽象类的充要条件。
创建的对象需要使用delete进行释放,但是不一定需要定义初始值。
友元无this指针原因:this指针是在指向类成员本身,但是友元并不是在类里面,而是在类外面;举个例子,有两个类A和B,函数C是这两个类的友元,假如C里面可以有this,那么this到底是类A的成员还是类B的成员?
(#define …),C++中应用内联实现(inline …),目的是为了提高函数的执行效率(速度),一般适用于短小且无限循环的函数。
int *ptr1[10]; //指针数组
int (*ptr2)[10]; //数组指针
指针数组:首先这个变量是一个数组,意思是说这个数组的所有元素都是指针类型
数组指针:数组指针可以说这是“数组的指针”,其次说这个指针存放着一个数组的首地址(或者说是指针指向了该数组的首地址)
指针是地址变量,存放的地址所指向的数据类型是指针变量的类型
const在指针符号左边,则指针指向的变量值不可通过指针改变(可以通过其他途径改变);在的右边,则指针的指向不可变,简记为“左定值,右定向”。
1、宏定义,其本质是文本替换;2、模板,模板在编译时生成对应的类/函数;3、内联,即在编译时替换成函数代码。但是,递归则是可能导致爆栈而非引起代码区问题。
static修饰的静态变量,不会随着函数运行结束而结束;但是局部变量在函数运行完毕后就释放了,再次调用需要再次赋初值;另一方面,区别于全局变量(所有函数都可以访问到全局变量),静态变量只能由当前定义它的函数访问。
是指某些虽然不是类成员却能够访问类的所有成员的函数(一定程度上,友元和类的封装特性相违背)
普通全局变量能被源文件函数访问,外部源文件函数访问时需要加extern关键字来声明全局变量;static全局变量只能被当前源文件访问(作用域是从定义位置开始到源文件结束);另一方面,若函数定义在全局变量之前,则不能访问全局变量。
(&)是指除了指针外另一个可以产生多态效果的手段,这意味着,一个基类的引用可以指向它的派生类实例。
包括:静态数据成员、非静态数据成员、静态成员函数、非静态成员函数。其中,成员函数(包括静态和非静态)和静态数据成员都是不占存储空间的。
对象大小 = 虚函数指针 + 所有非静态数据成员大小 + 因对齐而多占的字节
不论多少个虚函数,都有一个指向虚函数表的指针,占用8字节(64位系统)
在C++中,每个类都有且必须有构造函数,如果用户不编写,则C++自动提供一个默认无参构造函数,每个默认构造函数不做任何工作,一旦用户编写了构造函数则无参构造函数自行消失,如需要只能自己编写一个无参构造函数;在构造函数中:1、方法名必须和类名相同;2、方法名的前面没有返回值类型的声明;3、在方法名中不能使用return语句返回一个值。
成员函数被重载的特征:(1)相同的范围(在同一个类中)、(2)函数名字相同、(3)参数不同、(4)virtual关键字可有可无。
重写(覆盖)是指派生类函数覆盖基类函数,特征是:(1)不同的范围(分别位于派生类与基类)、(2)函数名字相同、(3)参数相同、(4)基类函数必须有virtual关键字。
(1)第一个const修饰返回类型(这里是无返回值类型,若是有返回值则是不可修改返回值);
(2)第二个const修饰参数,表示函数体内该参数不可被修改;
(3)第三个const在函数名后面,表示的是常成员函数,即只能在类中出现。
可以省略行数,但是不能省略列数,因为二维数组存储的时候是先行后列。
#include
#include
using namespace std;
class Pet {
string name;
public:
Pet(string p=" ") { name = p; }
string getName() const { return name; }
virtual void call() const = 0; //在这里指定虚函数
};
class Dog : public Pet{
public:
Dog(string n) : Pet(n) {}
void call() const { cout<< "##" << " "; }
};
class Cat : public Pet{
public:
Cat(string n) : Pet(n) {}
void call() const { cout << "**"; }
};
void f(Pet *p) {
p->call();
}
int main() {
Pet pet0("#"); //(1)
Dog pet1("*"); //(2)
Cat pet2("$"); //(3)
f(&pet1); //(4)
f(&pet2); //(5)
return 0;
}
注意!类Pet包含纯虚函数,是一个虚基类,不能直接实例化,因此注释(1)错误;(2)和(3)中Dog和Cat是派生类,实例化没有问题;(4)和(5)可以将派生类对象指针转换为指针使用,没问题。
对于const,可以作为函数重载判断依据,例如重载[]运算符时,有没有const区别是:有const只读、没有const读写都可以。
关联容器是树结构,无序关联容器是基于哈希表。所以set,map,multiset,multimap都是基于红黑树的;而unordered_set,unordered_map都是基于哈希表的。
全局变量定义在函数外部,可以被该源文件中所有的函数访问,因此可以用来传递数据;全局变量不能被外部函数访问;如果函数定义在全局变量之前则不能访问全局变量;全局变量作用域大于函数作用域。
int *p[3] 是一个指针数组,p是数组名,数组元素类型是 int *。即表示一个一维数组中存放3个指针变量,分别是p[0]、p[1]、p[2];int a[3] 是一个数组,a是数组名,数组元素类型是 int 。即表示一个一维数组中存放3个int类型的元素,分别是a[0]、a[1]、a[2] 。
不能给一个数组名(p)赋值,*p等价于 *(p+0)等价于p[0],而p[0]中存放的是指针变量(int *),a[0]是int型。
类的所有成员共享一个静态成员。静态类数据成员必须在类的内部声明,在类的外部初始化。在访问静态成员时,可以通过对象访问,也可以通过类访问。通过this指针访问的数据成员和方法成员都必须是类的非静态成员。静态成员可以作为默认实参,非静态成员不能(this指针,所有实例化的对象都共享一个静态成员,而非静态成员需要绑定到一个类对象或者指针上)
class a {
public:
static int i;
};
int main() {
a a1;
a1.i = 3; //对象修改静态变量
a::i = 4; //类直接修改静态变量
}
#include
class A
{
public:
A()
{
printf("1");
}
A(A &a)
{
printf("2");
}
A &operator=(const A &a)
{
printf("3");
return *this;
}
};
int main()
{
A a;
A b = a;
}
A a,定义一个对象,毫无疑问调用构造函数 A b=a,这是定义了对象b,且以a对b进行初始化,这个时候需要调用拷贝构造函数。 如果写成A a;A b;b=a;则是调用后面重载的赋值函数,这种情况应该输出113。 这个题主要考察赋值和初始化的区别。
*(a+1):直接用数组名a代表数组首元素的地址,+1表示引用下一个元素
&(a+1):表示偏移数组元素个位置,到末尾后一个位置
关键字inline必须与函数定义体放在一起才能使函数成为内联,仅将inline放在函数声明前不起任何作用,一般会把内联函数的定义放在头文件中,适用于规模较小(可理解为行数少)、流程直接(可理解为没有for循环)、频繁调用的函数。
在类中声明的函数一般默认隐式的inline,在类的声明处用inline关键字,可用于显示化成员函数,但是类外使用inline则必须跟函数实现体。
回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python等更现代的编程语言中还可以使用仿函数或匿名函数。
int sz = 5;
std::vector<int>nums{ 5,3,8,6,9,1,4,7,2 };
auto it2 = find_if(nums.begin(), nums.end(), [&](int i)->bool{return i > sz; });
std::cout << *it2 << std::endl; //结果为8
bool biggerthanz(int i, int j) {///用于bind的函数
return i > j;
}
///
int sz = 5;
std::vector<int>nums{ 5,3,8,6,9,1,4,7,2 };
auto it3 = find_if(nums.begin(), nums.end(), bind(biggerthanz, std::placeholders::_1, sz));
std::cout << *it3 << std::endl;
class biggerthan//用于仿函数,使一个类看上去像个函数
{
public:
biggerthan(int i) :x(i) {}
bool operator()(int n) {
return n > x;
}
private:
int x;
};
//
int sz = 5;
std::vector<int>nums{ 5,3,8,6,9,1,4,7,2 };
//仿函数重载()运算符
auto it1 = find_if(nums.begin(), nums.end(), biggerthan(sz));
std::cout << *it1 << std::endl;
C语言中static修饰静态变量或函数,在函数外定义,那么static修饰的变量在当前C程序中使用,如果在函数内部定义,这个变量只初始化一次,即使再次调用函数,这个static变量也不会被初始化。
虚类不可实例化
#include
#include
using namespace std;
class Pet {
string name;
public:
Pet(string p = " ") { name = p; }
string getName() const { return name; }
virtual void call() const = 0; //在这里指定虚函数
};
class Dog : public Pet{
public:
Dog(string n) : Pet(n) {}
void call() const { cout<< "*汪汪叫*" << " "; }
};
class Cat : public Pet{
public:
Cat(string n) : Pet(n) {}
void call() const { cout << "*喵喵叫*" << " "; }
};
void f(Pet *p) {
p->call();
}
int main() {
//Pet pet0("#"); //(1)
Dog pet1("*"); //(2)
Cat pet2("$"); //(3)
f(&pet1); //(4)
cout << endl;
f(&pet2); //(5)
return 0;
}
注意!类Pet包含纯虚函数,是一个虚基类,不能直接实例化,因此注释(1)错误;
(2)和(3)中Dog和Cat是派生类,实例化没有问题;
(4)和(5)可以将派生类对象指针转换为指针使用,没问题。
将(1)注释后输出结果:
构造函数
#include
class A
{
public:
A()
{
printf("1\n");
}
A(A &a)
{
printf("2\n");
}
A &operator=(const A &a)
{
printf("3\n");
return *this;
}
};
int main()
{
A a; //默认构造函数
A b = a; //拷贝构造函数
A c; //默认构造函数
c = a; //赋值重载函数
}
A a,A c定义一个对象,毫无疑问调用默认构造函数 。
A b = a,这是定义了对象b,且以a对b进行初始化,这个时候需要调用拷贝构造函数。
如果写成A c;c = a;则是调用后面重载的赋值函数。
这样输出结果会是1, 2, 1, 3
回调函数
回调函数就是一个被作为参数传递的函数。在C语言中,回调函数只能使用函数指针实现,在C++、Python等更现代的编程语言中还可以使用仿函数或匿名函数。
int sz = 5;
std::vector<int>nums{ 5,3,8,6,9,1,4,7,2 };
auto it2 = find_if(nums.begin(), nums.end(), [&](int i)->bool{return i > sz; });
std::cout << *it2 << std::endl; //结果为8
class biggerthan //用于仿函数,使一个类看上去像个函数
{
public:
biggerthan(int i) :x(i) {}
bool operator()(int n) {
return n > x;
}
private:
int x;
};
//
int sz = 5;
std::vector<int>nums{ 5,3,8,6,9,1,4,7,2 };
//仿函数重载()运算符
auto it1 = find_if(nums.begin(), nums.end(), biggerthan(sz));
std::cout << *it1 << std::endl; //结果为8
慢慢再补充~