相对于java,C++中的那些神奇语法
创始人
2024-01-17 14:06:45

空指针还可以调用成员函数

#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;
}

常函数与mutable成员变量

这个不算奇怪,一种限制成员变量修改的语法,在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这个类。

我们把HomeMyFriend的定义换一下位置:

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中使用到了MyFriendvisit函数,这是不行的,因为声明MyFriend只能表明有这样一个类,但是无法知道这个类里面有什么的,于是想到能否把MyFriendvisit函数也声明一下呢?如下:
在这里插入图片描述
如上图,是不是写法有问题,再如下面:
在这里插入图片描述
如上图,还是有问题,那我把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)函数了。

相关内容

热门资讯

埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
怎么样消灭臭虫 臭虫用什么药消... 奔奔虫控|被吸血臭虫骚扰的滋味,你体会过吗?相信被臭虫骚扰过的人都不想再体验一次了吧?那么臭虫应该怎...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...
应用未安装解决办法 平板应用未... ---IT小技术,每天Get一个小技能!一、前言描述苹果IPad2居然不能安装怎么办?与此IPad不...
脚上的穴位图 脚面经络图对应的... 人体穴位作用图解大全更清晰直观的标注了各个人体穴位的作用,包括头部穴位图、胸部穴位图、背部穴位图、胳...
长白山自助游攻略 吉林长白山游... 昨天介绍了西坡的景点详细请看链接:一个人的旅行,据说能看到长白山天池全凭运气,您的运气如何?今日介绍...
猫咪吃了塑料袋怎么办 猫咪误食... 你知道吗?塑料袋放久了会长猫哦!要说猫咪对塑料袋的喜爱程度完完全全可以媲美纸箱家里只要一有塑料袋的响...
世界上最漂亮的人 世界上最漂亮... 此前在某网上,选出了全球265万颜值姣好的女性。从这些数量庞大的女性群体中,人们投票选出了心目中最美...
埃菲尔铁塔在哪 中国仿建埃菲尔... 2019年4月26日,广西南宁市,街头惊现一座巨型山寨版埃菲尔铁塔,高约20米,白色塔身,造型逼真,...
苗族的传统节日 贵州苗族节日有... 【岜沙苗族芦笙节】岜沙,苗语叫“分送”,距从江县城7.5公里,是世界上最崇拜树木并以树为神的枪手部落...
苁蓉的食用方法 鲜苁蓉的吃法有... 肉苁蓉号称“温而不热,补而不峻,暖而不燥,滑而不泄,故有从容之名。”历来也是传统药食同源的滋补佳品。...
北京的名胜古迹 北京最著名的景... 北京从元代开始,逐渐走上帝国首都的道路,先是成为大辽朝五大首都之一的南京城,随着金灭辽,金代从海陵王...