既然找不到实习那就开始学习C++吧!?Day03
1.运算符重载
对已有运算符重新定义,以适应不同的数据结构。
1.运算符重载需要注意的规则
- 一下运算符不可重载关系运算符".“、成员指针运算符”.*“、作用域运算符”::“、sizeof运算符和三目运算符”?:"。
- 重载范围限制在已有运算符内,不可创建新的运算符。
- 运算符重载本质上是函数重载,遵守函数重载的规则
- 运算符重载不改变优先级和结合性,也不能改变运算符操作数的个数和语法结构。
- 运算符被重载后原有的意思并没有失去,只是定义了对于一个特定类的新运算符。
2.运算符重载的两个方法
- 重载为类成员函数
- 重载为友元函数
3.方法选择
- 当无法修改左操作数的类时,使用友元函数进行重载;
- =, [], ()和->操作符只能通过成员函数进行重载;
- <<和>>需要使用友元函数实现;
- 友元函数重载运算符常用于操作符左右数据类型不同的情况,如1+a(在成员函数中不合法),但a+1合法。
4.使用友元函数重载<<
class Person
{
friend ostream& operator<<(ostream& os, Person person);
public:
Person(int id, int age) {
mID = id;
mAge = age;
}
private:
int mID;
int mAge;
};
ostream& operator<<(ostream& os, Person person)
{
os << "ID:" << person.mID << "AGE:" << person.mAge;
return os;
}
int main() {
Person person(100, 10);
cout << person << endl;
system("pause");
return EXIT_SUCCESS;
}
5.自增自减(++/–)运算符重载
- ++a(前置++),它就调用operator++(a)
- a++(后置++),它就会去调用operator++(a,int)
class Complex
{
friend ostream& operator<<(ostream& os, Complex complex) {
os << "A:" << complex.mA << " B:" << complex.mB << endl;
return os;
}
public:
Complex() {
mA = 0;
mB = 0;
}
//使用成员函数重载
Complex& operator++() {
mA++;
mB++;
return *this;
}
Complex& operator--() {
mA--;
mB--;
return *this;
}
Complex operator++(int) {
Complex temp;
temp.mA = this->mA;
temp.mB = this->mB;
mA++;
mB++;
return temp;
}
Complex operator--(int) {
Complex temp;
temp.mA = mA;
temp.mB = mB;
mA--;
mB--;
return temp;
}
private:
int mA;
int mB;
};
int main() {
Complex complex;
complex++;
cout << complex;
//前置形式,对象先++或--,返回当前对象,返回的是新对象
++complex;
cout << complex;
//后置形式是先返回,然后对象++或者--,返回的是对象的原值
Complex ret = complex++;
cout << ret;
cout << complex;
cout << "------" << endl;
ret--;
--ret;
cout << "ret:" << ret;
complex--;
--complex;
cout << "complex:" << complex;
system("pause");
return EXIT_SUCCESS;
}
6.重载=运算符
- 类的默认拷贝构造函数属于“浅拷贝”,这样就会导致在类实例化对象之间进行赋值操作时可能产生内存泄露问题。
- 分别实例类对象obj1和obj2,当执行obj2=obj1时,obj2的指针就会指向obj1的内存空间,obj2原来内存空间泄露。
- 另外当obj1删除时会释放对应的内存空间,而此时obj2指向的内存空间和obj1相同,当obj2删除时会再次释放同一个内存空间,造成内存泄露。
class Person
{
friend ostream& operator<<(ostream& os, const Person& person) {
os << "Name:" << person.pName << " ID:" << person.mID << " Age:" << person.mAge << endl;
return os;
}
public:
Person(const char* name, int id, int age) {
this->pName = new char[strlen(name) + 1];
strcpy(this->pName, name);
this->mID = id;
this->mAge = age;
}
~Person() {
if (this->pName != NULL) {
delete[] this->pName;
}
}
Person& operator=(const Person& person) {
if (this->pName != NULL) {
delete[] this->pName;
}
this->pName = new char[strlen(person.pName) + 1];
strcpy(this->pName, person.pName);
this->mID = person.mID;
this->mAge = person.mAge;
return *this;
}
private:
char* pName;
int mID;
int mAge;
};
int main() {
Person person1("John", 20, 20);
Person person2("Edward", 30, 30);
cout << "person1:" << person1;
cout << "person2:" << person2;
person2 = person1;
cout << "person2:" << person2;
system("pause");
return EXIT_SUCCESS;
}
7.不要重载“||”和“&&”
- 这两个操作符实现了短路规则,而当重载这两个操作符是无法实现短路规则,导致函数中的参数都会被求值,导致实际结果与预期结果不符合。
8.总结
- =, [], () 和 -> 操作符只能通过成员函数进行重载
- << 和 **>>**只能通过全局函数配合友元函数进行重载
- 不要重载 && 和 || 操作符,因为无法实现短路规则
2.继承和派生
- C++最重要的特征是代码重用,通过继承机制可以使用已有的数据类型来定义新的数据类型。
- B类继承于A类,或者说A类派生B类,则A类为父类,B类为子类。
- 子类包含两部分成员
- 从父类继承来的成员,表现其共性。
- 新增的成员,表现其个性。
1.派生类定义
Class 派生类名 : 继承方式 基类名{
//派生类新增的数据成员和成员函数
}
- 三种继承方式
- public : 公有继承
- private : 私有继承
- protected : 保护继承
- C++可实现多继承
2.派生类访问控制
- 派生类继承基类,拥有基类中所有的成员变量和成员方法(除了构造方法和析构方法之外)
- 派生类中继承的成员不一定能够直接访问,不同的继承方式会导致不同的访问权限
//基类
class A{
public:
int mA;
protected:
int mB;
private:
int mC;
};
//1. 公有(public)继承
class B : public A{
public:
void PrintB(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubB : public B{
void PrintSubB(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test01(){
B b;
cout << b.mA << endl; //可访问基类public属性
//cout << b.mB << endl; //不可访问基类protected属性
//cout << b.mC << endl; //不可访问基类private属性
}
//2. 私有(private)继承
class C : private A{
public:
void PrintC(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubC : public C{
void PrintSubC(){
//cout << mA << endl; //不可访问基类public属性
//cout << mB << endl; //不可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test02(){
C c;
//cout << c.mA << endl; //不可访问基类public属性
//cout << c.mB << endl; //不可访问基类protected属性
//cout << c.mC << endl; //不可访问基类private属性
}
//3. 保护(protected)继承
class D : protected A{
public:
void PrintD(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
class SubD : public D{
void PrintD(){
cout << mA << endl; //可访问基类public属性
cout << mB << endl; //可访问基类protected属性
//cout << mC << endl; //不可访问基类private属性
}
};
void test03(){
D d;
//cout << d.mA << endl; //不可访问基类public属性
//cout << d.mB << endl; //不可访问基类protected属性
//cout << d.mC << endl; //不可访问基类private属性
}
3.继承中的构造与析构
-
继承中的对象模型
C++中的子类是由父类成员叠加子类新成员形成的。
class Aclass{ public: int mA; int mB; }; class Bclass : public Aclass{ public: int mC; }; class Cclass : public Bclass{ public: int mD; }; void test(){ cout << "A size:" << sizeof(Aclass) << endl; cout << "B size:" << sizeof(Bclass) << endl; cout << "C size:" << sizeof(Cclass) << endl; } //结果 //A size:8 //B size:12 //C size:16
-
继承中的构造与析构
- 子类对象在创建时会首先调用父类的构造函数
- 父类构造函数执行完毕后,才会调用子类的构造函数
- 当父类构造函数有参数时,需要在子类初始化列表(参数列表)中显示调用父类构造函数
- 析构函数调用顺序和构造函数相反
class A{ public: A(){ cout << "A类构造函数!" << endl; } ~A(){ cout << "A类析构函数!" << endl; } }; class B : public A{ public: B(){ cout << "B类构造函数!" << endl; } ~B(){ cout << "B类析构函数!" << endl; } }; class C : public B{ public: C(){ cout << "C类构造函数!" << endl; } ~C(){ cout << "C类析构函数!" << endl; } }; void test(){ C c; } //结果 //A类构造函数! //B类构造函数! //C类构造函数! //C类析构函数! //B类析构函数! //A类析构函数!
-
当子类对象有其他对象作为成员时,构造函数的执行顺序为:父类、对象成员、子类
class D{ public: D(){ cout << "D类构造函数!" << endl; } ~D(){ cout << "D类析构函数!" << endl; } }; class A{ public: A(){ cout << "A类构造函数!" << endl; } ~A(){ cout << "A类析构函数!" << endl; } }; class B : public A{ public: B(){ cout << "B类构造函数!" << endl; } ~B(){ cout << "B类析构函数!" << endl; } }; class C : public B{ public: C(){ cout << "C类构造函数!" << endl; } ~C(){ cout << "C类析构函数!" << endl; } public: D c; }; void test(){ C c; } //结果 //A类构造函数! //B类构造函数! //D类构造函数! //C类构造函数! //C类析构函数! //D类析构函数! //B类析构函数! //A类析构函数!
4.继承中同名成员的处理方法
-
当子类成员和父类成员同名时,子类依然从父类继承同名成员
-
如果子类有成员和父类同名,子类访问其成员默认访问子类的成员(本作用域,就近原则)
-
在子类通过作用域::进行同名成员区分(在派生类中使用基类的同名成员,显示使用类名限定符)
class Base{ public: Base():mParam(0){} void Print(){ cout << mParam << endl; } public: int mParam; }; class Derived : public Base{ public: Derived():mParam(10){} void Print(){ //在派生类中使用和基类的同名成员,显示使用类名限定符 cout << Base::mParam << endl; cout << mParam << endl; } //返回基类重名成员 int& getBaseParam(){ return Base::mParam; } public: int mParam; }; int main(){ Derived derived; //派生类和基类成员属性重名,子类访问成员默认是子类成员 cout << derived.mParam << endl; //10 derived.Print(); //类外如何获得基类重名成员属性 derived.getBaseParam() = 100; cout << "Base:mParam:" << derived.getBaseParam() << endl; return EXIT_SUCCESS; }
-
Derive1 重定义了Base类的myfunc函数,derive1可访问func1及其重载版本的函数。
-
Derive2通过改变函数参数列表的方式重新定义了基类的func1函数,则从基类中继承来的其他重载版本被隐藏,不可访问
-
Derive3通过改变函数返回类型的方式重新定义了基类的func1函数,则从基类继承来的没有重新定义的重载版本的函数将被隐藏。
注意: 如果重新定义了基类中的重载函数,将会发生什么? class Base{ public: void func1(){ cout << "Base::void func1()" << endl; }; void func1(int param){ cout << "Base::void func1(int param)" << endl; } void myfunc(){ cout << "Base::void myfunc()" << endl; } }; class Derived1 : public Base{ public: void myfunc(){ cout << "Derived1::void myfunc()" << endl; } }; class Derived2 : public Base{ public: //改变成员函数的参数列表 void func1(int param1, int param2){ cout << "Derived2::void func1(int param1,int param2)" << endl; }; }; class Derived3 : public Base{ public: //改变成员函数的返回值 int func1(int param){ cout << "Derived3::int func1(int param)" << endl; return 0; } }; int main(){ Derived1 derived1; derived1.func1(); derived1.func1(20); derived1.myfunc(); cout << "-------------" << endl; Derived2 derived2; //derived2.func1(); //func1被隐藏 //derived2.func1(20); //func2被隐藏 derived2.func1(10,20); //重载func1之后,基类的函数被隐藏 derived2.myfunc(); cout << "-------------" << endl; Derived3 derived3; //derived3.func1(); 没有重新定义的重载版本被隐藏 derived3.func1(20); derived3.myfunc(); return EXIT_SUCCESS; }
5.非自动继承的函数
- 构造函数和析构函数不会被继承,每一个特定的派生类分别创建。
- operate=也不能被继承,因为他完成了类似于构造函数的行为
6.继承中的静态成员特性
-
他们都可以被继承到派生类中。
-
如果重新定义一个静态成员函数,所有在基类中的其他重载函数会被隐藏。
-
如果我们改变基类中一个函数的特征,所有使用该函数名的基类版本都会被隐藏。
-
静态成员函数不能是虚函数(virtual function)
class Base{ public: static int getNum(){ return sNum; } static int getNum(int param){ return sNum + param; } public: static int sNum; }; int Base::sNum = 10; class Derived : public Base{ public: static int sNum; //基类静态成员属性将被隐藏 #if 0 //重定义一个函数,基类中重载的函数被隐藏 static int getNum(int param1, int param2){ return sNum + param1 + param2; } #else //改变基类函数的某个特征,返回值或者参数个数,将会隐藏基类重载的函数 static void getNum(int param1, int param2){ cout << sNum + param1 + param2 << endl; } #endif }; int Derived::sNum = 20;
3.多继承
1.多继承概念
-
我们可以从一个类继承,我们也可以能同时从多个类继承,这就是多继承。但是由于多继承是非常受争议的,从多个类继承可能会导致函数、变量等同名导致较多的歧义。
-
解决方法就是显示指定调用那个基类的版本。
class Base1{ public: void func1(){ cout << "Base1::func1" << endl; } }; class Base2{ public: void func1(){ cout << "Base2::func1" << endl; } void func2(){ cout << "Base2::func2" << endl; } }; //派生类继承Base1、Base2 class Derived : public Base1, public Base2{}; int main(){ Derived derived; //func1是从Base1继承来的还是从Base2继承来的? //derived.func1(); derived.func2(); //解决歧义:显示指定调用那个基类的func1 derived.Base1::func1(); derived.Base2::func1(); return EXIT_SUCCESS; }
2.菱形继承
-
两个派生类继承同一个基类而又有某个类同时继承者两个派生类,这种继承被称为菱形继承,或者钻石型继承。
-
羊继承了动物的数据和函数,鸵同样继承了动物的数据和函数,当草泥马调用函数或者数据时,就会产生二义性。
-
草泥马继承自动物的函数和数据继承了两份,其实我们应该清楚,这份数据我们只需要一份就可以。
class BigBase{ public: BigBase(){ mParam = 0; } void func(){ cout << "BigBase::func" << endl; } public: int mParam; }; class Base1 : public BigBase{}; class Base2 : public BigBase{}; class Derived : public Base1, public Base2{}; int main(){ Derived derived; //1. 对“func”的访问不明确 //derived.func(); //cout << derived.mParam << endl; cout << "derived.Base1::mParam:" << derived.Base1::mParam << endl; cout << "derived.Base2::mParam:" << derived.Base2::mParam << endl; //2. 重复继承 cout << "Derived size:" << sizeof(Derived) << endl; //8 return EXIT_SUCCESS; }
3.虚基类
-
使用虚基类解决重复继承和二义性问题
class BigBase{ public: BigBase(){ mParam = 0; } void func(){ cout << "BigBase::func" << endl; } public: int mParam; }; class Base1 : virtual public BigBase{}; class Base2 : virtual public BigBase{}; class Derived : public Base1, public Base2{}; int main(){ Derived derived; //二义性问题解决 derived.func(); cout << derived.mParam << endl; //输出结果:12 cout << "Derived size:" << sizeof(Derived) << endl; return EXIT_SUCCESS; }
-
虚基类实现原理
- Base1和Base2通过虚继承的方式派生自BigBase,编译器为的对象中增加了一个vbptr (virtual base pointer),vbptr指向了一张表,这张表保存了当前的虚指针相对于虚基类的首地址的偏移量。
- Derived派生于Base1和Base2,继承了两个基类的vbptr指针,并调整了vbptr与虚基类的首地址的偏移量。
- 在模型就变成了Base1和 Base2 Derived三个类对象共享了一份BigBase数据。
4.多态
1.静态多态和动态多态
- c++支持编译时多态(静态多态)和运行时多态(动态多态),运算符重载和函数重载就是编译时多态,而派生类和虚函数实现运行时多态。
- 静态多态和动态多态的区别就是函数地址是早绑定(静态联编)还是晚绑定(动态联编)。如果函数的调用,在编译阶段就可以确定函数的调用地址,并产生代码,就是静态多态(编译时多态),就是说地址是早绑定的。而如果函数的调用地址不能编译不能在编译期间确定,而需要在运行时才能决定,这这就属于晚绑定(动态多态,运行时多态)。
2.向上类型转换及问题
-
问题抛出
-
父类引用或指针可以指向子类对象,通过父类指针或引用来操作子类对象。
class Animal{ public: void speak(){ cout << "动物在唱歌..." << endl; } }; class Dog : public Animal{ public: void speak(){ cout << "小狗在唱歌..." << endl; } }; void DoBussiness(Animal& animal){ animal.speak(); } void test(){ Dog dog; DoBussiness(dog); } //结果:动物在唱歌 //问题抛出: 我们给DoBussiness传入的对象是dog,而不是animal对象,输出的结果应该是Dog::speak。
-
解决思路
- 绑定:把函数体与函数调用相联系称为绑定
- 当绑定在程序运行之前(由编译器和连接器)完成时,称为早绑定(early binding).C语言中只有一种函数调用方式,就是早绑定。
- 上述问题就是早绑定引起的。编译是根据指向对象的指针或引用的类型来选择函数调用。这个时候由于DoBussiness的参数类型是Animal&,编译器确定了应该调用的speak是Animal::speak的,而不是真正传入的对象Dog::speak。
- 使用迟绑定解决。
-
使用虚函数解决问题
- 为创建一个需要动态绑定的虚成员函数,可以简单在这个函数声明前面加上virtual关键字,定义时候不需要.
- 如果一个函数在基类中被声明为virtual,那么在所有派生类中它都是virtual的.
- 在派生类中virtual函数的重定义称为重写(override).
- Virtual关键字只能修饰成员函数.
- 构造函数不能为虚函数
class Animal{ public: virtual void speak(){ cout << "动物在唱歌..." << endl; } }; class Dog : public Animal{ public: virtual void speak(){ cout << "小狗在唱歌..." << endl; } }; void DoBussiness(Animal& animal){ animal.speak(); } void test(){ Dog dog; DoBussiness(dog); }
-
3.C++如何实现动态绑定
-
当编译器发现类中有虚函数的时候,编译器会创建一张虚函数表,把虚函数的函数入口地址放到虚函数表中,并且在类中秘密增加一个指针,vpointer(缩写vptr),这个指针是指向对象的虚函数表。
-
在多态调用的时候,根据vptr指针,找到虚函数表来实现动态绑定。
class A { public: virtual void func1() {} virtual void func2() {} }; //B类为空,那么大小应该是1字节,实际情况是这样吗? class B : public A {}; void test() { cout << "A size:" << sizeof(A) << endl; cout << "B size:" << sizeof(B) << endl; } int main() { test(); } //结果: //A size : 8 //B size : 8
4.抽象基类和纯虚函数
- 在基类中加入至少一个纯虚函数(pure virtual function),使得基类称为抽象类(abstract class).
- 纯虚函数使用关键字virtual,并在其后面加上=0。如果试图去实例化一个抽象类,编译器则会阻止这种操作。
- 当继承一个抽象类的时候,必须实现所有的纯虚函数,否则由抽象类派生的类也是一个抽象类。
- Virtual void fun() = 0;告诉编译器在vtable中为函数保留一个位置,但在这个特定位置不放地址。
5.虚析构函数
-
虚析构函数是为了解决基类的指针指向派生类对象,并用基类的指针删除派生类对象。
class People { public: People() { cout << "构造函数 People!" << endl; } virtual void showName() = 0; // 若不用虚函数则test()中无法析构worker virtual ~People() { cout << "析构函数 People!" << endl; } }; class Worker : public People { public: Worker() { cout << "构造函数 Worker!" << endl; pName = new char[10]; } virtual void showName() { cout << "打印子类的名字!" << endl; } ~Worker() { cout << "析构函数 Worker!" << endl; if (pName != NULL) { delete pName; } } private: char* pName; }; void test() { People* people = new Worker; people->~People(); }
-
纯虚析构函数
- 纯虚析构函数在c++中是合法的,但必须为纯虚析构函数提供一个函数体。
- 纯虚析构函数和非纯析构函数之间唯一的不同之处在于纯虚析构函数使得基类是抽象类,不能创建基类的对象。
5.重写 重载 重定义
重载,同一作用域的同名函数
- 同一个作用域
- 参数个数,参数顺序,参数类型不同
- 和函数返回值,没有关系
- const也可以作为重载条件 //do(const Teacher& t){} do(Teacher& t)
重定义(隐藏)
- 有继承
- 子类(派生类)重新定义父类(基类)的同名成员(非virtual函数)
重写(覆盖)
- 有继承
- 子类(派生类)重写父类(基类)的virtual函数
- 函数返回值,函数名字,函数参数,必须和基类中的虚函数一致
6.指向类成员的指针
1.指向成员变量的指针
-
定义格式
<数据类型> <类名>::*<指针名> 例如: int A::*pPram;
-
赋值/初始化
<数据类型> <类名>::*<指针名> = &<类名>::<非静态数据成员> 例如: int A::*pParam = &A::param;
-
解引用
<类对象名>.*<非静态数据成员指针> <类对象指针>->*<非静态数据成员指针> 例如: A a; a.*pParam; a->*pParam;
class A { public: A(int param) { mParam = param; } public: int mParam; }; void test() { A a1(100); A* a2 = new A(200); int* p1 = &a1.mParam; int A::* p2 = &A::mParam; cout << "*p1:" << *p1 << endl; cout << "a1.*p2:" << a1.*p2 << endl; cout << "a2->*p2:" << a2->*p2 << endl; } int main() { test(); } //结果: //*p1:100 //a1.*p2 : 100 //a2->*p2 : 200
2.指向成员函数的指针
-
定义格式
<返回类型> (<类名>::*<指针名>)(<参数列表>) 例如: void (A::*pFunc)(int,int);
-
赋值/初始化
<返回类型>(<类名>::*<指针名>)(<参数列表>) = &<类名>::<非静态数据函数> 例如: void (A::pFunc)(int,int) = &A::func;
-
解引用
(<类对象名>.*<非静态成员函数>)(<参数列表>) (<类对象指针>->*<非静态成员函数>)(<参数列表>) 例如: A a; (a.*pFunc)(10,20); (a->*pFunc)(10,20);
class A { public: int func(int a, int b) { return a + b; } }; void test() { A a1; A* a2 = new A; //初始化成员函数指针 int(A:: * pFunc)(int, int) = &A::func; //指针解引用 cout << "(a1.*pFunc)(10,20):" << (a1.*pFunc)(10, 20) << endl; cout << "(a2->*pFunc)(10,20):" << (a2->*pFunc)(10, 20) << endl; } int main() { test(); } //结果: //(a1.*pFunc)(10, 20) : 30 //(a2->*pFunc)(10, 20) : 30
3.指向静态成员的指针
-
指向类静态数据成员的指针
指向静态数据成员的指针的定义和使用与普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联。
-
指向类静态成员函数的指针
指向静态成员函数的指针和普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联·
class A { public: static void dis() { cout << data << endl; } static int data; }; int A::data = 100; void test() { int* p = &A::data; cout << *p << endl; void(*pfunc)() = &A::dis; pfunc(); } int main() { test(); } //结果: //100 //100
nc)(10,20);
class A {
public:
int func(int a, int b) {
return a + b;
}
};
void test() {
A a1;
A* a2 = new A;
//初始化成员函数指针
int(A:: * pFunc)(int, int) = &A::func;
//指针解引用
cout << "(a1.*pFunc)(10,20):" << (a1.*pFunc)(10, 20) << endl;
cout << "(a2->*pFunc)(10,20):" << (a2->*pFunc)(10, 20) << endl;
}
int main() {
test();
}
//结果:
//(a1.*pFunc)(10, 20) : 30
//(a2->*pFunc)(10, 20) : 30
#### 3.指向静态成员的指针
1. 指向类静态数据成员的指针
指向静态数据成员的指针的定义和使用与普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联。
2. 指向类静态成员函数的指针
指向静态成员函数的指针和普通指针相同,在定义时无须和类相关联,在使用时也无须和具体的对象相关联·
class A {
public:
static void dis() {
cout << data << endl;
}
static int data;
};
int A::data = 100;
void test() {
int* p = &A::data;
cout << *p << endl;
void(*pfunc)() = &A::dis;
pfunc();
}
int main() {
test();
}
//结果:
//100
//100