空指针还可以调用成员函数
#include <cstdio>
class Person {
public:
void sayHello() {
printf("hello!\n");
}
};
int main() {
auto * p = new Person;
p->sayHello();
p = nullptr;
p->sayHello();
return 0;
}
运行结果如下:
hello!
hello!
进程已结束,退出代码为 0
代码正常运行无异常,函数正常调用正常输出,真是神奇。
需要注意的是,空指针只能调用那些没有使用成员变量的函数,否则就会抛出异常,如下:
class Person {
public:
int age = 18;
void sayHello() {
printf("hello! age = %d\n", age);
}
};
再次运行代码,结果如下:
hello! age = 18
进程已结束,退出代码为 -1073741819 (0xC0000005)
可以看到,第二次调用sayHello()
函数时程序就挂掉了,成员变量还包括this
指针,如果是空指针,则this
也会是空,所以可以在函数中通过this
判断是否是空指针调用,如下:
class Person {
public:
int age = 18;
void sayHello() {
if (this == nullptr) {
printf("hello!\n");
} else {
printf("hello! age = %d\n", age);
}
}
};
再次运行,结果如下:
hello! age = 18
hello!
进程已结束,退出代码为 0
经过这样的处理,即便函数中有成员变量,也可以在空指针中正常调用该函数了。
类中的静态成员语法
如上截图,使用的IDE是CLion
,在声明静态变量时不能直接赋值,提示非静态数据成员必须在行外初始化,如下:
class Person {
public:
static int age;
};
int Person::age = 18;
int main() {
printf("age = %d\n", Person::age);
return 0;
}
对于从java转过来学习C的,这语法真的是恶心到我了。
可以直接在文件中声明一个全局静态变量,如下:
class Person {
public:
static int age;
};
int Person::age = 18;
static int count = 5;
int main() {
printf("age = %d\n", Person::age);
return 0;
}
相对而言,类中的静态变量比文件级的静态变量在使用时多了一个类名限定符,且类中的静态变量可以加private
等的修饰符。
对于静态函数,函数体可以在类中定义,也可以在类外定义,如下:
class Person {
public:
static int age;
static void hello() {
printf("hello\n");
}
static void go();
};
int Person::age = 18;
void Person::go() {
printf("go go go!");
}
int main() {
Person::hello();
Person::go();
return 0;
}
常函数与mutable成员变量
这个不算奇怪,一种限制成员变量修改的语法,在java中没有,也记录一下:
在函数后面加上const
修饰,则在这个函数内不能修改成员变量,如果有某个变量确实需要修改,则在这个变量上加入mutable
修饰符,示例如下:
#include <cstdio>
class Person {
public:
int age = 18;
mutable int count = 0;
void fun1() {
age = 19;
count = 19;
}
void fun2() const {
//age = 20; // 无法修改
count = 20; // mutable修饰的变量可以修改
}
};
int main() {
Person p;
p.fun1();
printf("age = %d, count = %d\n", p.age, p.count);
p.fun2();
printf("age = %d, count = %d\n", p.age, p.count);
return 0;
}
运行结果如下:
age = 19, count = 19
age = 19, count = 20
另外,在创建对象时,在对象类型的前面也可以加入const
修饰,修饰后就不可修改成员变量了,但mutable
类型的变量可以,在调用函数时,只能调用常函数,示例如下:
class Person {
public:
int age = 18;
mutable int count = 0;
void fun1() {
}
void fun2() const {
}
};
int main() {
Person p;
p.age = 45;
p.count = 45;
p.fun1();
p.fun2();
const Person p2; // 常对象
//p2.age = 50; // 无法修改
p2.count = 50; // mutable声明的变量可以赋值
//p2.fun1(); // 无法调用
p2.fun2(); // 常函数可以调用
return 0;
}
友元
全局函数做友元
示例代码如下:
#include <iostream>
using namespace std;
class Home {
public:
string livingRoom = "客厅";
private:
string bedroom = "卧室";
};
void visit(const Home &home) {
cout << "正在访问: " << home.livingRoom << endl; // 公有成员可以访问
//cout << "正在访问: " << home.bedroom << endl; // 私有成员无法访问
}
int main() {
visit(Home());
return 0;
}
运行结果如下:
正在访问: 客厅
如上代码,Home
类中有一个public
类型的livingRoom
成员变量,还有一个private
类型的bedroom
成员变量,visit
是一个全局函数,不是Home
类的成员函数,所以在visit
函数中,不能访问Home
对象中的私有成员,就如同现实生活中,客人来了可以访问客厅,但是自己的卧室是比较隐私的,一般不想让客人进入,但是也会有个别的好朋友你是愿意他进入的。代码中也一样,有时候也希望让某些类外的方法也能访问私有的成员,此时可以把这个类外的函数设置为友元,这样它就能访问类中的私有成员了,如下:
#include <iostream>
using namespace std;
class Home {
// 设置友元
friend void visit(const Home &home);
public:
string livingRoom = "客厅";
private:
string bedroom = "卧室";
};
void visit(const Home &home) {
cout << "正在访问: " << home.livingRoom << endl; // 公有成员可以访问
cout << "正在访问: " << home.bedroom << endl; // 友元可以访问私有成员
}
int main() {
visit(Home());
return 0;
}
运行结果如下:
正在访问: 客厅
正在访问: 卧室
类做友元
#include <iostream>
using namespace std;
class MyFriend;
class Home {
// 设置友元
friend void visit(const Home &home);
friend MyFriend;
public:
string livingRoom = "客厅";
private:
string bedroom = "卧室";
};
class MyFriend {
public:
void visit(const Home &home) {
cout << "正在访问: " << home.livingRoom << endl;
cout << "正在访问: " << home.bedroom << endl;
}
};
void visit(const Home &home) {
cout << "正在访问: " << home.livingRoom << endl;
cout << "正在访问: " << home.bedroom << endl;
}
int main() {
visit(Home());
MyFriend myFriend;
myFriend.visit(Home());
return 0;
}
成员函数做友元
可以设置只允许类中的某个函数做友元,如下:
#include <iostream>
#include <iostream>
using namespace std;
class Home;
class MyFriend {
public:
void visit(const Home &home);
};
class Home {
friend void MyFriend::visit(const Home &home);
public:
string livingRoom = "客厅";
private:
string bedroom = "卧室";
};
void MyFriend::visit(const Home &home) {
cout << "正在访问: " << home.livingRoom << endl;
cout << "正在访问: " << home.bedroom << endl;
}
int main() {
MyFriend().visit(Home());
return 0;
}
注意如下代码:
class Home;
class MyFriend {
public:
void visit(const Home &home);
};
因为Home
定义在MyFriend
的后面,而在MyFriend
中又用到了Home
,所以在前面使用class Home
声明一下,告诉编译器有Home
这个类。
我们把Home
和MyFriend
的定义换一下位置:
class MyFriend;
class Home {
friend void MyFriend::visit(const Home &home);
public:
string livingRoom = "客厅";
private:
string bedroom = "卧室";
};
class MyFriend {
public:
void visit(const Home &home);
};
void MyFriend::visit(const Home &home) {
cout << "正在访问: " << home.livingRoom << endl;
cout << "正在访问: " << home.bedroom << endl;
}
int main() {
MyFriend().visit(Home());
return 0;
}
这个代码是有问题的,无法编译,在CLion中显示错误如下:
这是因为虽然在前面声明了class MyFriend;
,但在Home
中使用到了MyFriend
的visit
函数,这是不行的,因为声明MyFriend
只能表明有这样一个类,但是无法知道这个类里面有什么的,于是想到能否把MyFriend
的visit
函数也声明一下呢?如下:
如上图,是不是写法有问题,再如下面:
如上图,还是有问题,那我把visit的定义写到前面呢?如下:
如上图,也是不行。所以,结论是Home必须定义在MyFriend
后面,这奇怪的语法真是令人难记,头痛,怎么记得住谁要定义在谁的前面。再来看正确的代码,如下:
#include <iostream>
#include <iostream>
using namespace std;
class Home;
class MyFriend {
public:
void visit(const Home &home);
};
class Home {
friend void MyFriend::visit(const Home &home);
public:
string livingRoom = "客厅";
private:
string bedroom = "卧室";
};
void MyFriend::visit(const Home &home) {
cout << "正在访问: " << home.livingRoom << endl;
cout << "正在访问: " << home.bedroom << endl;
}
int main() {
MyFriend().visit(Home());
return 0;
}
这里visit是定义在类外面的,我们把它定义到类里面,此时就又报错了,如下:
这是因为Home
是定义在MyFriend
后面的,所以在MyFriend
中无法知道Home
中的成员。
后面经过跟公司同事交流,说真实开发中没人会把类都写一个文件,一般是每个类单独写一个头文件和cpp源文件,这样在使用include的时候就不会有这个问题,代码如下:
Home.h
如下:
#ifndef WINDOWSAPP_HOME_H
#define WINDOWSAPP_HOME_H
#include "MyFriend.h"
#include <iostream>
using namespace std;
class Home {
friend void MyFriend::visit(const Home &home);
public:
string livingRoom = "客厅";
private:
string bedroom = "卧室";
};
#endif
MyFriend.h
如下:
#ifndef WINDOWSAPP_MYFRIEND_H
#define WINDOWSAPP_MYFRIEND_H
class Home;
class MyFriend {
public:
void visit(const Home & home);
};
#endif
MyFriend.cpp
如下:
#include "MyFriend.h"
#include "Home.h"
void MyFriend::visit(const Home & home) {
cout << "MyFriend正在访问: " << home.livingRoom << endl;
cout << "MyFriend正在访问: " << home.bedroom << endl;
}
main.cpp
如下:
#include "Home.h"
using namespace std;
int main() {
MyFriend myFriend;
myFriend.visit(Home());
return 0;
}
此时运行代码是正常的。
注意,我们在MyFriend.h
中声明了Home
类:class Home;
,既然都单独写头文件了,为何不直接include呢,于是修改为如下:
#ifndef WINDOWSAPP_MYFRIEND_H
#define WINDOWSAPP_MYFRIEND_H
#include "Home.h"
class MyFriend {
public:
void visit(const Home & home);
};
#endif
改了这个之后,MyFriend.cpp
中就报错了,如下:
这是因为两个类不能互相包含,详情可看后面的知识
两个对象相互包含
A.h
如下:
#pragma once
#include <iostream>
#include "B.h"
class A
{
public:
B b;
};
B.h
如下:
#pragma once
#include <iostream>
#include "A.h"
class B
{
public:
A a;
};
当我们创建一个A
对象的时候,如:A a;
由于A里面需要创建一个B
,而创建B时又需要创建A,这会导致死循环创建,就如同Java中的如下代码:
public class A {
public B b = new B();
}
public class B {
public A a = new A();
}
解决的办法就是不要在声明的时候就直接赋值,修改为如下:
public class A {
public B b;
}
public class B {
public A a;
}
后期再给A、B中的成员属性赋值就可以了,C++中也一样,可以把成员变量声明为指针,这样就不会一开始就创建对象了,如下:
A.h
如下:
#pragma once
#include <iostream>
#include "B.h"
class A
{
public:
B * b;
};
B.h
如下:
#pragma once
#include <iostream>
#include "A.h"
class B
{
public:
A * a;
};
这个代码在IDE中是没有报错的,但是运行时会报错,这是因为A和B相互include
,虽然前面有#pragma once,还是不行,所以要改为类声明,如下:
A.h
如下:
#pragma once
#include <iostream>
class B;
class A
{
public:
B * b;
};
B.h
如下:
#pragma once
#include <iostream>
class A;
class B
{
public:
A * a;
};
这样就没问题了,main.cpp
如下:
#include "A.h"
#include "B.h"
int main()
{
A a;
B b;
a.b = &b;
b.a = &a;
}
只要不是两个头文件互相包含,也是可以的,比如在A中include B,此时在B中就不能inlclude A了,只能使用class A;
来声明一下。一般来说,在头文件里面,能使用类声明,就不要使用include,迫不得已的情况下才使用include,比如,A类在设置B类中的某个方法为友元时就需要使用include,如下:
#pragma once
#include "MyFriend.h"
#include <iostream>
using namespace std;
class Home {
friend void MyFriend::visit(const Home &home);
public:
string livingRoom = "客厅";
private:
string bedroom = "卧室";
};
如上代码,我们在Home
中设置MyFriend
类的visit(const Home &home)
函数为友元函数,以便该函数可以访问Home
中的私有变量,所以在Home
中,我们需要知道MyFriend
类,还需要知道它有一个函数叫visit(const Home &home)
,所以需要#include "MyFriend.h"
,如果只是使用class MyFriend;
来声明一下,这就无法知道MyFriend
类中的visit(const Home &home)
函数了。