title: c++基础知识点
date: 2018-07-16 15:07:29
updated: 2020-03-24 13:13:29
categories: c++
1.局部变量初始化
当局部变量被定义时,系统不会对其初始化,您必须自行对其初始化。
2.宏与const对比
const 相比 #define 的优点:
const 常量有数据类型,而 #define 没有,编译器可以对前者进行类型安全检查,而对后者只能进行字符替换,没有安全检查,并且在字符替换时候可能导致意想不到的错误。
void f1 ()
{
#define N 12
const int n 12;
}
void f2 ()
{
cout<<N <<endl; //正确,N已经定义过,不受定义域限制
cout<<n <<endl; //错误,n定义域只在f1函数中
}
宏定义可以通过#undef来使之前的宏定义失效;const常量定义后将在定义域内永久有效
void f1()
{
#define N 12
const int n = 12;
#undef N //取消宏定义后,即使在f1函数中,N也无效了
#define N 21//取消后可以重新定义
}
宏定义不能作为参数传递给函数;const常量可以在函数的参数列表中出现
const限定符定以后是不可以改变的,所以在定义时必须赋初始值,要不然是错误的,除非这个变量是用extern修饰的外部变量。 例如:
const int A=10; //正确。
const int A; //错误,没有赋初始值。
extern const int A; //正确,使用extern的外部变量。
const修饰成员变量
const int data;
const 修饰的成员变量必须在构造方法的参数列表初始化(const static int pdata=10;除外)const 修饰的成员变量不能被修改
const修饰成员方法
void showData()const{ }
const 修饰的成员函数中不能修改成员变量,不能调用非 const 修饰的函数
3.static
存储类指示编译器在程序的生命周期内保持局部变量的存在,而不需要在每次它进入和离开作用域时进行创建和销毁。因此,使用 static 修饰局部变量可以在函数调用之间保持局部变量的值。
#include <iostream>
using namespace std;
// 函数声明
void func(void);
static int count = 10; /* 全局变量 */
int main()
{
while(count--)
{
func();
}
return 0;
}
// 函数定义
void func( void ) {
static int i = 5; // 局部静态变量
i++;
cout << "变量 i 为 " << i;
cout << " , 变量 count 为 " << count << endl;
}
output:
变量 i 为 6 , 变量 count 为 9
变量 i 为 7 , 变量 count 为 8
变量 i 为 8 , 变量 count 为 7
变量 i 为 9 , 变量 count 为 6
变量 i 为 10 , 变量 count 为 5
变量 i 为 11 , 变量 count 为 4
变量 i 为 12 , 变量 count 为 3
变量 i 为 13 , 变量 count 为 2
变量 i 为 14 , 变量 count 为 1
变量 i 为 15 , 变量 count 为 0
通常,在函数体内定义了一个变量,每当程序运行到该语句时都会给该局部变量分配栈内存。但随着程序退出函数体,系统就会收回栈内存,局部变量也相应失效。但有时候我们需要在两次调用之间对变量的值进行保存。
通常的想法是定义一个全局变量来实现。但这样一来,变量已经不再属于函数本身了,不再仅受函数的控制,给程序的维护带来不便。静态局部变量正好可以解决这个问题。静态局部变量保存在全局数据区,而不是保存在栈中,每次的值保持到下一次调用,直到下次赋新值。
静态局部变量有以下特点:
该变量在全局数据区分配内存;
静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;
static 修饰符也可以应用于全局变量。当 static 修饰全局变量时,
在 C++ 中,当 static 用在类数据成员上时,会导致仅有一个该成员的副本被类的所有对象共享。
1.static 修饰类的成员变量
1). 静态成员变量是先于类的对象而存在
2). 这个类的所有对象共用一个静态成员
3). 如果静态成员是公有的,那么可以直接通过类名调用
4). 静态成员数据在声明时候类外初始化
2.static 修饰类的成员方法
1). 静态成员函数是先于类的对象而存在
2). 可用类名直接调用(公有)
3). 在静态成员函数中没有this指针,所以不能使用非静态成员
4.extern存储类
extern 存储类用于提供一个全局变量的引用,全局变量对所有的程序文件都是可见的。当您使用 ‘extern’ 时,对于无法初始化的变量,会把变量名指向一个之前定义过的存储位置。
当您有多个文件且定义了一个可以在其他文件中使用的全局变量或函数时,可以在其他文件中使用 extern 来得到已定义的变量或函数的引用。可以这么理解,extern 是用来在另一个文件中声明一个全局变量或函数,extern 修饰符通常用于当有两个或多个文件共享相同的全局变量或函数的时候。如下所示:
第一个文件:main.cpp
#include <iostream>
int count ;
extern void write_extern();
int main()
{
count = 5;
write_extern();
}
第二个文件:support.cpp
#include <iostream>
extern int count;
void write_extern(void)
{
std::cout << "Count is " << count << std::endl;
}
编译如下:
g++ main.cpp support.cpp -o write
5.thread_local存储类
使用 thread_local 说明符声明的变量仅可在它在其上创建的线程上访问。 变量在创建线程时创建,并在销毁线程时销毁。 每个线程都有其自己的变量副本。
thread_local 说明符可以与 static 或 extern 合并。
可以将 thread_local 仅应用于数据声明和定义,thread_local 不能用于函数声明或定义。
以下演示了可以被声明为 thread_local 的变量:
thread_local int x; // 命名空间下的全局变量
class X
{
static thread_local std::string s; // 类的static成员变量
};
static thread_local std::string X::s; // X::s 是需要定义的
void foo()
{
thread_local std::vector<int> v; // 本地变量
}
6.无限循环
C++ 程序员偏向于使用 for(;? 结构来表示一个无限循环
#include <iostream>
using namespace std;
int main ()
{
for( ; ; )
{
printf("This loop will run forever.\n");
}
return 0;
}
注意:您可以按 Ctrl + C 键终止一个无限循环。
7.复制内存
memcpy()用来复制内存到另一个位置。
#include<iostream>
#include<cstring> //memcpy from cstring
using namespace std;
int main()
{
int src =10;
int dst;
memcpy(&dst, &src, 4); //memcpy()用来复制内存到另一个位置
cout<<dst<<endl; //dst=10
return 0;
}
8.函数参数
如果函数要使用参数,则必须声明接受参数值的变量。这些变量称为函数的形式参数。
形式参数就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
- 传值调用 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数对实际参数没有影响。
- 指针调用 该方法把参数的地址复制给形式参数。在函数内,该地址用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
- 引用调用 该方法把参数的引用复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。
默认情况下,C++ 使用传值调用来传递参数。一般来说,这意味着函数内的代码不能改变用于调用函数的参数。之前提到的实例,调用 max() 函数时,使用了相同的方法。
9.lambda函数与表达式
如果你的代码里面存在大量的小函数,而这些函数一般只被调用一次,那么不妨将他们重构成 lambda 表达式。
lambda表达式是C++11最重要也最常用的一个特性之一。lambda来源于函数式编程的概念,也是现代编程语言的一个特点。函数式编程简介:简单说,“函数式编程”是一种“编程范式”。它属于“结构化编程”的一种,主要思想是把运算过程尽量写成一系列嵌套的函数调用。
lambda表达式的语法归纳如下:
[ caputrue ] ( params ) opt -> ret { body; };
1).capture是捕获列表;
2).params是参数表;(选填)
3).opt是函数选项;可以填mutable,exception,attribute(选填)
mutable说明lambda表达式体内的代码可以修改被捕获的变量,并且可以访问被捕获的对象的non-const方法。
exception说明lambda表达式是否抛出异常以及何种异常。
attribute用来声明属性。
4).ret是返回值类型。(选填)
5).body是函数体。
捕获列表:lambda表达式的捕获列表精细控制了lambda表达式能够访问的外部变量,以及如何访问这些变量。
1).[]不捕获任何变量。
2).[&]捕获外部作用域中所有变量,并作为引用在函数体中使用(按引用捕获)。
3).[=]捕获外部作用域中所有变量,并作为副本在函数体中使用(按值捕获)。
4).[=,&foo]按值捕获外部作用域中所有变量,并按引用捕获foo变量。
5).[bar]按值捕获bar变量,同时不捕获其他变量。
6).[this]捕获当前类中的this指针,让lambda表达式拥有和当前类成员函数同样的访问权限。如果已经使用了&或者=,就默认添加此选项。捕获this的目的是可以在lamda中使用当前类的成员函数和成员变量。
几个帮助理解的例子:
a.
#include<iostream>
using namespace std;
int main()
{
auto f = [](int x, int y){return x>y?x:y;};
cout<<f(1,2)<<endl; //output:2
}
b.
#include<iostream>
using namespace std;
int main()
{
int a = 10;
auto f = [=](int x, int y){return a + x + y;}; //捕获[a]也行
cout<<f(1, 2)<<endl; //output:13
return 0;
}
c.
#include<iostream>
using namespace std;
int main()
{
auto f1 = [](int x, int y)->int{return x+y;};
cout<<f1(1, 2)<<endl; //output:3
return 0;
}
d.
#include<iostream>
using namespace std;
int main()
{
int a = 10;
auto f1 = [&a](int x)->int{ a++; return x+a;};
cout<<a<<" "<<f1(1)<<" "<<a<<endl; //output:10 12 11
return 0;
}
在Lambda表达式内可以访问当前作用域的变量,这是Lambda表达式的闭包(Closure)行为。 与JavaScript闭包不同,C++变量传递有传值和传引用的区别。可以通过前面的[]来指定:
[] // 沒有定义任何变量。使用未定义变量会引发错误。
[x, &y] // x以传值方式传入(默认),y以引用方式传入。
[&] // 任何被使用到的外部变量都隐式地以引用方式加以引用。
[=] // 任何被使用到的外部变量都隐式地以传值方式加以引用。
[&, x] // x显式地以传值方式加以引用。其余变量以引用方式加以引用。
[=, &z] // z显式地以引用方式加以引用。其余变量以传值方式加以引用。
10.c++ 迭代器
考虑下面的需求,对vector中的每个元素加1
a. 原始办法
/**
* 功能描述:对vector<int>中的每个元素加1
* @Author:braincao
* @Date: 2018/1/6 16:18
*/
#include <iostream>
#include <vector>
using namespace std;
void add(int &b) //利用引用把b的值增加1
{
b+=1;
}
int main()
{
int data[] = {1,2,3,4,5};
vector<int> d(data, data+5);
for(vector<int>::iterator iter=d.begin(); iter!=d.end(); iter++)
add(*iter); //原始方法遍历
for(int i:d)
cout<<i<<" "; //output:2 3 4 5 6
return 0;
}
b.使用的for_each
/**
* 功能描述:对vector<int>中的每个元素加1
* @Author:braincao
* @Date: 2018/1/6 16:18
*/
#include <iostream>
#include <vector>
#include <algorithm>
using namespace std;
void add(int &b) //利用引用把b的值增加1
{
b += 1;
}
int main()
{
int data[] = {1,2,3,4,5};
vector<int> d(data, data+5);
for_each(d.begin(), d.end(), add); //使用<algorithm>的for_each
for(int i:d)
cout<<i<<" "; //output:2 3 4 5 6
return 0;
}
11.映射map
/**
* 功能描述:映射map的示例
* @Author:braincao
* @Date: 2018/1/6 19:27
*/
#include<iostream>
#include <map>
using namespace std;
int main()
{
map<char, int> count;
char c;
do{
cin>>c;
if(isalpha(c))
{
c = tolower(c);
count[c]++; //在count中查找键为c的元素,如果存在,返回它的附加数据的引用;
// 如果不存在,则向count中插入一个新元素并返回该元素的附加数据的引用,该附加数据的初值为V(),其中V为附加数据的类型
}
}while(c != '.');
map<char, int>::iterator iter = count.begin();
for(map<char, int>::iterator iter=count.begin(); iter!=count.end(); iter++)
cout<<iter->first<<" "<<iter->second<<endl;
return 0;
}
input:
this is what we sow.
output:
a 1
e 1
h 2
i 2
o 1
s 3
t 2
w 3
12.map和vector的迭代器失效问题
当删除一个STL容器(比如map, vector)中的某个元素时, 会引起迭代器失效, 所以, 我们务必提高警惕。
题目: 删除map<int, int>中value为5的倍数的元素。 该题看起来很自然很简单, 实则有迭代器失效的陷阱。
如果对迭代器失效问题一无所知, 则很容易写出如下的错误代码:
#include <iostream>
#include <map>
using namespace std;
typedef map<int, int> Map;
typedef map<int, int>::iterator MapIt;
void print(Map &m)
{
MapIt it;
for(it = m.begin(); it != m.end(); it++)
{
cout << it->second << " ";
}
cout << endl;
}
void deleteValueFromMap(Map &m, int n = 5)
{
MapIt it;
for(it = m.begin(); it != m.end(); it++)
{
if(0 == it->second % n)
{
m.erase(it); //m.erase(it)后,it就失效了,而for循环中有it++, error
}
}
}
int main()
{
Map m;
int i = 0;
for(i = 0; i < 21; i++)
{
m[i] = i;
}
print(m);
deleteValueFromMap(m); // 程序崩溃
return 0;
}
运行上述程序, 结果程序崩溃,什么原因呢? 原来, 对于关联的容器map来说, m.erase(it);后, it就失效了, 而for循环中有it++, 自然而然会出问题啊。 那怎么办呢? 且看:
#include <iostream>
#include <map>
using namespace std;
typedef map<int, int> Map;
typedef map<int, int>::iterator MapIt;
void print(Map &m)
{
MapIt it;
for(it = m.begin(); it != m.end(); it++)
{
cout << it->second << " ";
}
cout << endl;
}
void deleteValueFromMap(Map &m, int n = 5)
{
MapIt it;
for(it = m.begin(); it != m.end(); /*不能再自增了*/)
{
if(0 == it->second % n)
{
m.erase(it++); //it++先执行, 此时还没有erase, 程序自然不会崩溃. 当it++执行完后, 已经指向了下一个元素了, 但it++的返回值还是当前元素, 此时再删除它
}
else
{
it++;
}
}
}
int main()
{
Map m;
int i = 0;
for(i = 0; i < 21; i++)
{
m[i] = i;
}
print(m);
deleteValueFromMap(m); // 程序ok
print(m);
return 0;
}
map的迭代器失效问题先介绍到这里,下面是vector迭代器问题:
void deleteValueFromVector(Vec &v, int n = 5)
{
VecIt it;
for(it = v.begin(); it != v.end(); /*不能再自增了*/)
{
if(0 == *it % n)
{
v.erase(it); // vector元素自动向前挪动了(关联的map容器元素不会自动挪动), 所以不能再把it进行++了
}
else
{
it++;
}
}
}
13.C++模板(template)
/**
* 功能描述:c++模板示例
* @Author:braincao
* @Date: 2018/1/7 00:21
*/
#include <iostream>
using namespace std;
template<typename T> //typename换成class也行
T max1(T a, T b)
{
return (a>b)?a:b;
}
int main()
{
int a = 5;
int b = 10;
cout<<max1(a, b)<<endl; //output:10
return 0;
}
14.c++的浅拷贝与深拷贝
深拷贝和浅拷贝可以简单理解为:如果一个类拥有资源,当这个类的对象发生复制过程的时候,资源重新分配,这个过程就是深拷贝,反之,没有重新分配资源,就是浅拷贝。
什么时候用到拷贝函数?
- a.一个对象以值传递的方式传入函数体;
- b.一个对象以值传递的方式从函数返回;
- c.一个对象需要通过另外一个对象进行初始化。
如果在类中没有显式地声明一个拷贝构造函数,那么,编译器将会自动生成一个默认的拷贝构造函数,该构造函数完成对象之间的位拷贝。位拷贝又称浅拷贝
15.c++深入理解虚函数
#include <iostream>
using namespace std;
class Shape
{
public:
double cArea()
{
cout<<"cArea"<<" ";
return 0;
}
};
class Circle:public Shape
{
public:
Circle(double r)
{
m_dr = r;
};
double cArea();
private:
double m_dr;
};
double Circle::cArea()
{
return 3.14*m_dr*m_dr;
}
class Rect:public Shape
{
public:
Rect(double h, double w){
m_h = h;
m_w = w;
}
double cArea();
private:
double m_h;
double m_w;
};
double Rect::cArea()
{
return m_h*m_w;
}
int main()
{
Shape* a = new Circle(4.0);
Shape* b = new Rect(3.0, 5.0);
a->cArea();
b->cArea(); //output:cArea cArea,并没有实现动态绑定的多态
return 0;
}
从结果看,调用到的都是父类的calcArea函数,并不是我们想要的那样去分别调用各自的计算面积的函数。如果要想实现动态多态必须使用虚函数。用virtual去修饰成员函数使其成为虚函数:
#include <iostream>
using namespace std;
class Shape
{
public:
virtual double cArea()
{
cout<<"cArea"<<" ";
return 0;
}
};
class Circle:public Shape
{
public:
Circle(double r)
{
m_dr = r;
};
virtual double cArea();
private:
double m_dr;
};
double Circle::cArea()
{
cout<<"Circle: "<<3.14*m_dr*m_dr<<endl;
return 3.14*m_dr*m_dr;
}
class Rect:public Shape
{
public:
Rect(double h, double w){
m_h = h;
m_w = w;
}
virtual double cArea();
private:
double m_h;
double m_w;
};
double Rect::cArea()
{
cout<<"Rect: "<<m_h*m_w<<endl;
return m_h*m_w;
}
int main()
{
Shape* a = new Circle(4.0);
Shape* b = new Rect(3.0, 5.0);
a->cArea();
b->cArea(); //output: Circle: 50.24 Rect: 15
return 0;
}
成功实现动态绑定
15.1 虚析构函数
之前是使用virtual去修饰成员函数,这里使用virtual去修饰析构函数,部分代码如下
class Shape
{
public:
....
virtual ~Shape();
private:
....
};
class Circle:public Shape
{
public:
virtual ~Circle();//与虚函数相同,此处virtual可以不写,系统将会自动添加,建议写上
....
};
....
这样父类指针指向的是哪个对象,哪个对象的构造函数就会先执行,然后执行父类的构造函数。销毁的时候子类的析构函数也会执行。virtual关键字可以修饰普通的成员函数,也可以修饰析构函数,但并不是没有限制。
virtual在函数中的使用限制
- 普通函数不能是虚函数,也就是说这个函数必须是某一个类的成员函数,不可以是一个全局函数,否则会导致编译错误。
- 静态成员函数不能是虚函数 static成员函数是和类同生共处的,他不属于任何对象,使用virtual也将导致错误。
- 内联函数不能是虚函数 如果修饰内联函数 如果内联函数被virtual修饰,计算机会忽略inline使它变成存粹的虚函数。
- 构造函数不能是虚函数,否则会出现编译错误。
15.2 虚函数实现原理
首先,什么是函数指针?
指针指向对象称为对象指针,指针除了指向对象还可以指向函数,函数的本质就是一段二进制代码,我们可以通过指针指向这段代码的开头,计算机就会从这个开头一直往下执行,直到函数结束,并且通过指令返回回来。函数的指针与普通的指针本质上是一样的,也是由四个基本的内存单元组成,存储着内存的地址,这个地址就是函数的首地址。
多态的实现原理
虚函数表指针:类中除了定义的函数成员,还有一个成员是虚函数表指针(占四个基本内存单元),这个指针指向一个虚函数表的起始位置,这个表会与类的定义同时出现,这个表存放着该类的虚函数指针,调用的时候可以找到该类的虚函数表指针,通过虚函数表指针找到虚函数表,通过虚函数表的偏移找到函数的入口地址,从而找到要使用的虚函数。
当实例化一个该类的子类对象的时候,(如果)该类的子类并没有定义虚函数,但是却从父类中继承了虚函数,所以在实例化该类子类对象的时候也会产生一个虚函数表,这个虚函数表是子类的虚函数表,但是记录的子类的虚函数地址却是与父类的是一样的。所以通过子类对象的虚函数表指针找到自己的虚函数表,在自己的虚函数表找到的要执行的函数指针也是父类的相应函数入口的地址。
如果我们在子类中定义了从父类继承来的虚函数,对于父类来说情况是不变的,对于子类来说它的虚函数表与之前的虚函数表是一样的,但是此时子类定义了自己的(从父类那继承来的)相应函数,所以它的虚函数表当中管于这个函数的指针就会覆盖掉原有的指向父类函数的指针的值,换句话说就是指向了自己定义的相应函数,这样如果用父类的指针,指向子类的对象,就会通过子类对象当中的虚函数表指针找到子类的虚函数表,从而通过子类的虚函数表找到子类的相应虚函数地址,而此时的地址已经是该函数自己定义的虚函数入口地址,而不是父类的相应虚函数入口地址,所以执行的将会是子类当中的虚函数。这就是多态的原理。
函数的覆盖和隐藏
父类和子类出现同名函数称为隐藏。
父类对象.函数函数名(...); //调用父类的函数
子类对象.函数名(...); //调用子类的函数
子类对象.父类名::函数名(...);//子类调用从父类继承来的函数。
父类和子类出现同名虚函数称为覆盖
父类指针=new 子类名(...);父类指针->函数名(...);//调用子类的虚函数。
虚析构函数的实现原理
虚析构函数的特点:
当我们在父类中通过virtual修饰析构函数之后,通过父类指针指向子类对象,通过delete接父类指针就可以释放掉子类对象
理论前提:
执行完子类的析构函数就会执行父类的析构函数
原理:
如果父类当中定义了虚析构函数,那么父类的虚函数表当中就会有一个父类的虚析构函数的入口指针,指向的是父类的虚析构函数,子类虚函数表当中也会产生一个子类的虚析构函数的入口指针,指向的是子类的虚析构函数,这个时候使用父类的指针指向子类的对象,delete接父类指针,就会通过指向的子类的对象找到子类的虚函数表指针,从而找到虚函数表,再虚函数表中找到子类的虚析构函数,从而使得子类的析构函数得以执行,子类的析构函数执行之后系统会自动执行父类的虚析构函数。这个是虚析构函数的实现原理。
纯虚函数
纯虚函数的定义:纯虚函数没有函数体,同时在定义的时候函数名后面要加“=0”
class A
{
public:
virtual double cArea(){....} //虚函数
virtual double calcPerimeter() = 0; //纯虚函数
};
纯虚函数的实现原理:
在虚函数原理的基础上,虚函数表中,虚函数的地址是一个有意义的值,如果是纯虚函数就实实在在的写一个0。
含有纯虚函数的类被称为抽象类:
含有纯虚函数的类被称为抽象类,比如上面代码中的类就是一个抽象类,包含一个计算周长的纯虚函数。哪怕只有一个纯虚函数,那么这个类也是一个抽象类,纯虚函数没有函数体,所以抽象类不允许实例化对象,抽象类的子类也可以是一个抽象类。抽象类子类只有把抽象类当中的所有的纯虚函数都做了实现才可以实例化对象。
对于抽象的类来说,我们往往不希望它能实例化,因为实例化之后也没什么用,而对于一些具体的类来说,我们要求必须实现那些要求(纯虚函数),使之成为有具体动作的类。
含有纯虚函数的类称为接口类:
如果在抽象类当中仅含有纯虚函数而不含其他任何东西,我们称之为接口类。
- 1.没有任何数据成员
- 2.仅有成员函数
- 3.成员函数都是纯虚函数
16.const与*
口诀:左定值,右定向
即 const在*的左边不能改变字符串常量的值;const int * a;
const在*的右边不能改变指针的指向;int * const a;
17.c++面经题
结论:虚函数的动态绑定仅在基类指针或引用绑定派生类对象时发生
#include<iostream>
using namespace std;
class B0//基类BO声明
{
public://外部接口
virtual void display()//虚成员函数
{
cout<<"B0::display0"<<endl;}
};
class B1:public B0//公有派生
{
public:
void display() { cout<<"B1::display0"<<endl; }
};
class D1: public B1//公有派生
{
public:
void display(){ cout<<"D1::display0"<<endl; }
};
void fun(B0 ptr)//普通函数
{
ptr.display();
}
int main()//主函数
{
B0 b0;//声明基类对象和指针
B1 b1;//声明派生类对象
D1 d1;//声明派生类对象
fun(b0);//调用基类B0函数成员
fun(b1);//调用派生类B1函数成员
fun(d1);//调用派生类D1函数成员
}
output:
B0::display0
B0::display0
B0::display0
分析:
这里使用的不是按地址传递,这样会转化为基类对象,直接调用基类的成员函数,输出如上。
如果是指针传递,改为B0 *ptr,ptr->display(),可以实现多态。虚函数的动态绑定仅在基类指针或引用绑定派生类对象时发生 ,fun的形参不是指针,所以调用哪个版本的函数编译时就已经确定,根据形参静态类型确定调用B0的成员。
重写代码实现多态:
#include<iostream>
using namespace std;
class B0//基类BO声明
{
public://外部接口
virtual void display()//虚成员函数
{
cout<<"B0::display0"<<endl;}
};
class B1:public B0//公有派生
{
public:
void display() { cout<<"B1::display0"<<endl; }
};
class D1: public B1//公有派生
{
public:
void display(){ cout<<"D1::display0"<<endl; }
};
void fun(B0 *ptr)//形参改为指针
{
ptr->display();
}
int main()//主函数
{
B0 b0;//声明基类对象和指针
B1 b1;//声明派生类对象
D1 d1;//声明派生类对象
fun(&b0); //
fun(&b1);//实参变为取地址
fun(&d1);
}
output:
B0::display0
B1::display0
D1::display0
再次重申结论:虚函数的动态绑定仅在基类指针或引用绑定派生类对象时发生