#include 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;
}
这个不算奇怪,一种限制成员变量修改的语法,在java中没有,也记录一下:
在函数后面加上const修饰,则在这个函数内不能修改成员变量,如果有某个变量确实需要修改,则在这个变量上加入mutable修饰符,示例如下:
#include 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
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
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
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 #include
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 #include
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
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_Hclass 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
#include "B.h"
class A
{
public:B b;
};
B.h如下:
#pragma once
#include
#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
#include "B.h"
class A
{
public:B * b;
};
B.h如下:
#pragma once
#include
#include "A.h"
class B
{
public:A * a;
};
这个代码在IDE中是没有报错的,但是运行时会报错,这是因为A和B相互include,虽然前面有#pragma once,还是不行,所以要改为类声明,如下:
A.h如下:
#pragma once
#include
class B;
class A
{
public:B * b;
};
B.h如下:
#pragma once
#include
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
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)函数了。
上一篇:[SpringBoot] AOP-AspectJ 切面技术
下一篇:杂说易龙说