1、C++ 静态成员
静态成员就是在成员变量和成员函数前加上关键字static,静态成员分为:
静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量,不能访问非个静态成员变量
访问静态成员方法:
- 通过对象访问:对象名.静态成员
- 通过类名访问: 类名::静态成员
静态成员也受访问权限限制:可以访问public权限的静态成员,不可以访问private的静态成员
#include <iostream>
using namespace std;
//类
class Person
{
//静态成员变量
public: //访问权限 - 公共权限
//类内声明
static int staticA;
private://访问权限 - 私有权限
static int staticB;
public:
//非静态成员变量
int nonStatic;
public: //访问权限 - 公共权限
static void f1()
{
cout << "静态成员函数f1,访问静态成员变量 staticA = " << staticA << endl;
//nonStatic = 10; //访问非静态成员,报错:非静态成员引用必须与特定成对象相对(如果使用类名调用静态成员函数时,无法区分是哪个对象成员属性nonStatic)
cout << "静态成员函数f1,不能访问非静态成员变量 nonStatic = " << endl;
}
private://访问权限 - 私有权限
static void f2()
{
cout << "私有静态成员函数f1,访问静态成员变量 staticA = " << staticA << endl;
}
};
//静态成员变量 - 类外初始化
int Person::staticA = 5;
int Person::staticB = 3;
int main()
{
//类和对象 - 静态成员
cout << endl << " ---- 静态成员变量 ---- " << endl;
//静态成员变量
//通过对象访问静态成员变量
Person p1;
cout << "通过对象访问静态成员变量, p1.staticA = " << p1.staticA << endl;
Person p2;
p2.staticA = 21;
cout << "静态成员变量p2对象修改后, p1.staticA = " << p1.staticA << endl;
//通过类名访问静态成员变量
cout << "通过类名访问静态成员变量, Person::staticA = " << Person::staticA << endl;
//p1.staticB; //报错:不可访问
//Person::staticB;//报错:不可访问
cout << "不能访问private权限的静态成员变量 Person::staticB" << endl;
cout << endl << " ---- 静态成员函数 ---- " << endl;
//静态成员函数
//通过对象访问静态成员函数
p1.f1();
//通过类名访问静态成员函数
Person::f1();
//p1.f2(); //报错:不可访问
//Person::f2(); //报错:不可访问
cout << "不能访问private权限的静态成员函数 Person::f2()" << endl;
system("pause");
return 0; //程序
}
输出结果
---- 静态成员变量 ----
通过对象访问静态成员变量, p1.staticA = 5
静态成员变量p2对象修改后, p1.staticA = 21
通过类名访问静态成员变量, Person::staticA = 21
不能访问private权限的静态成员变量 Person::staticB---- 静态成员函数 ----
静态成员函数f1,访问静态成员变量 staticA = 21
静态成员函数f1,不能访问非静态成员变量 nonStatic =
静态成员函数f1,访问静态成员变量 staticA = 21
静态成员函数f1,不能访问非静态成员变量 nonStatic =
不能访问private权限的静态成员函数 Person::f2()
2、C++ 对象模型
类内的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象上。
- 非静态成员变量 - 占用对象空间
- 静态成员变量 - 不占用对象空间
- 非静态成员函数 - 不占用对象空间
- 静态成员函数 - 不占用对象空间
#include <iostream>
using namespace std;
//类 - 无成员
class Empty
{
};
//类
class Person
{
public:
//非静态成员变量 - 占用对象空间
int nonStatic;
//静态成员变量 - 不占用对象空间
static int staticA;
//非静态成员函数 - 不占用对象空间
void f1()
{
cout << "非静态成员函数" << endl;
}
//静态成员函数 - 不占用对象空间
static void f2()
{
cout << "静态成员函数" << endl;
}
};
//静态成员变量 - 类外初始化
int Person::staticA = 5;
int main()
{
//类和对象 - 静态成员
Empty e1;
cout << "没有成员类对象占用空间 = " << sizeof(e1) << endl;
Person p1;
cout << "有成员对象占用空间(与非静态成员变量有关) = " << sizeof(p1) << endl;
system("pause");
return 0;
}
输出结果
没有成员类对象占用空间 = 1
有成员对象占用空间(与非静态成员变量有关) = 4
3、C++ this指针
类内的成员变量与成员函数是分开存储的,每一个非静态成员函数只会产生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针 - this指针,解决这个问题。
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途
- 当形参和成员变量同名时,可用this指针区分
- 在类的非静态成员函数中返回对象本身,可使用return *this;
#include <iostream>
using namespace std;
//类
class ClassA
{
private:
//非静态成员变量
int count;
public:
//非静态成员函数 - 不占用对象空间
void setCount(int count)
{
//当形参和成员变量同名时,可用this指针区分
this->count = count;
}
int getCount()
{
return count;
}
ClassA& add()
{
count++;
//在类的非静态成员函数中返回对象本身,可使用return *this;
//this指向的是对象的指针,*this指向就是对象本身
return *this;
}
ClassA add2()
{
count++;
return *this;
}
};
int main()
{
//类和对象 - this指针
/*
当形参和成员变量同名时,可用this指针区分
在类的非静态成员函数中返回对象本身,可使用return *this;
*/
ClassA c1;
c1.setCount(0);
//链式编程
c1.add().add().add();
cout << "add成员函数返回引用:count = " << c1.getCount() << endl;
c1.setCount(0);
c1.add2().add2().add2();
cout << "add2成员函数返回值:count = " << c1.getCount() << endl;
system("pause");
return 0;
}
输出结果
add成员函数返回引用:count = 3
add2成员函数返回值:count = 1
4、C++ 空指针访问成员函数
C++中空指针是可以调用成员函数的,但要注意有没有用到this指针,如果成员函数中用到了this指针,需要判断保证代码的健壮性。
#include <iostream>
using namespace std;
//类
class Person
{
public:
//非静态成员变量
string name;
void print()
{
cout << "成员函数中不包含this指针" << endl;
}
void print2()
{
//空指针对象访问时会报错,空指针对象不能访问成员变量
cout << "成员函数中包含this指针, this->name = " << this->name << endl;
}
void print3()
{
//解决空指针对象访问时会报错方法:增加是否是空指针的判断
if (this == NULL)
{
return;
}
cout << "成员函数中包含this指针,增加空指针判断避免异常, this->name = " << this->name << endl;
}
};
int main()
{
//类和对象 - this指针 - 空指针访问成员函数
//空指针对象
Person *p1 = NULL;
p1->print();
//p1->print2(); //程序运行后抛出异常:this 是 nullptr
p1->print3();
system("pause");
return 0;
}
输出结果
成员函数中不包含this指针
5、C++ const修饰成员函数
常函数
- 成员函数后加const称为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中可以修改
this 的本质
this 的本质是指针常量, 指针常量不可以修改指针的指向,可以修改指针指向的值
this本质:Person* const this
在成员函数后加上const为常函数,即 const Person* const thisconst即修饰指针又修改常量,此时指针的指向不可以修改,指针指向的值也不可以修改
之前学到的引用的本质也是指针常量,关于指针常量可参考:C++ 学习(七)指针、const修饰指针、指针与数组、指针与函数_瘦身小蚂蚁的博客-CSDN博客
常对象
- 声明对象前加const为常对象
- 常对象只能调用常函数
#include <iostream>
using namespace std;
//类
class Person
{
public:
string name;
mutable int age;
//构造函数,不加会在运行时创建常对象位置报错
Person() {
}
//常函数 - 成员函数后加const
/*
this 的本质是指针常量, 指针常量不可以修改指针的指向,可以修改指针指向的值
this本质:Person* const this
在成员函数后加上const为常函数,即 const Person* const this //const即修饰指针又修改常量
此时指针的指向不可以修改,指针指向的值也不可以修改
*/
void show() const
{
//常函数内不可以修改成员属性
//name = "Tracy"; //修饰成员属性,报错:操作数类型为 const std::string = const char[6]
//成员属性声明时加关键字mutable后,在常函数中可以修改
age = 20;
}
void show2()
{
age = 27;
}
};
int main()
{
//类和对象 - this指针 - const修饰成员函数
/*
* 常函数
成员函数后加const称为常函数
常函数内不可以修改成员属性
成员属性声明时加关键字mutable后,在常函数中可以修改
*/
Person p1;
p1.show();
/*
* 常对象
声明对象前加const为常对象
常对象只能调用常函数
常对象不可以修改成员属性
成员属性声明时加关键字mutable后,在常对象可以修改
*/
const Person p2;
//p2.name = "Tracy"; //常对象不可以修改成员属性,报错:操作数类型为 const std::string = const char[6]
//成员属性声明时加关键字mutable后,在常对象可以修改
p2.age = 24;
cout << "成员属性声明时加关键字mutable后,在常对象可以修改, p2.age = " << p2.age << endl;
//常对象只能调用常函数
p2.show();
cout << "常对象调用常函数后, p2.age = " << p2.age << endl;
//p2.show2();// 常对象调用非常函数时,报错:对象含有与成员 函数“Person::show2”不兼容的类型限定符
system("pause");
return 0;
}
输出结果
成员属性声明时加关键字mutable后,在常对象可以修改, p2.age = 24
常对象调用常函数后, p2.age = 20
遇到的问题
不加构造函数,在执行时 const Person p; 报错: p必须初始化const对象,在Person类中加了构造函数后运行正常,目前还不知道为什么会这样?
//构造函数,不加会在运行时创建常对象位置报错
Person() {}
6、C++ 友元(friend)
友元是站一个函数或类访问另一个类中的私有成员。关键字为friend。
友元的三种实现
- 全局函数作友元
- 类作友元
- 成员函数作友元
6.1、全局函数作友元
#include <iostream>
using namespace std;
//类
class Person
{
//全局函数作友元
friend void goodFirend(Person* person);
public:
//构造函数
Person(string _name, string _phone) :name(_name), phone(_phone)
{
}
public: //公有成员
string name;
private: //私有成员
string phone;
};
//全局函数
void goodFriend(Person * person)
{
cout << "全局函数访问对象公有成员:name = " << person->name << endl;
cout << "全局函数访问对象私有成员:phone = " << person->phone << endl;
}
int main()
{
//类和对象 - 友元 - 全局函数作友元
Person p("Tracy", "13800000000");
//调用全局函数
goodFriend(&p);
system("pause");
return 0;
}
输出结果
全局函数访问对象公有成员:name = Tracy
全局函数访问对象私有成员:phone = 13800000000
6.2、类作友元
#include <iostream>
using namespace std;
//类
class Person
{
//类作友元
friend class GoodFriend;
public:
//构造函数
Person(string _name, string _phone);
public: //公有成员
string name;
private: //私有成员
string phone;
};
class GoodFriend
{
public:
Person* person;
GoodFriend();
void visit();
};
Person::Person(string _name, string _phone) : name(_name), phone(_phone)
{
}
GoodFriend::GoodFriend()
{
person = new Person("Timo", "13900000000");
}
//类对象访问对象私有成员
void GoodFriend::visit()
{
cout << "类对象访问对象公有成员:name = " << person->name << endl;
cout << "类对象访问对象私有成员:phone = " << person->phone << endl;
}
int main()
{
//类和对象 - 友元 - 类作友元
GoodFriend gf;
gf.visit();
system("pause");
return 0;
}
输出结果
类对象访问对象公有成员:name = Timo
类对象访问对象私有成员:phone = 13900000000
6.3、成员函数作友元
注意以下类Person与类GoodFriend声明与定义顺序。
#include <iostream>
using namespace std;
class Person; //引用声明
class GoodFriend
{
private:
Person* person;
public:
GoodFriend();
void visit(); //能够访问类对象中的私有成员
void visit2(); //不能访问类对象中的私有成员
};
//类
class Person
{
public:
//成员函数作友元
friend void GoodFriend::visit();
public:
//构造函数
Person(string _name, string _phone);
public: //公有成员
string name;
private: //私有成员
string phone;
};
Person::Person(string _name, string _phone) : name(_name), phone(_phone)
{
}
GoodFriend::GoodFriend()
{
person = new Person("Timo", "13900000000");
}
//成员函数访问对象私有成员
void GoodFriend::visit()
{
cout << "成员函数访问对象公有成员:name = " << person->name << endl;
cout << "成员函数访问对象私有成员:phone = " << person->phone << endl;
}
//成员函数不能访问对象私有成员
void GoodFriend::visit2()
{
cout << "成员函数访问对象公有成员:name = " << person->name << endl;
//cout << "成员函数访问对象私有成员:phone = " << person->phone << endl;
}
int main()
{
//类和对象 - 友元 - 成员函数作友元
GoodFriend gf;
gf.visit();
gf.visit2();
system("pause");
return 0;
}
输出结果
成员函数访问对象公有成员:name = Timo
成员函数访问对象私有成员:phone = 13900000000
成员函数访问对象公有成员:name = Timo
7、C++ 运算符重载(operator)
对已有的运算符重新定义,赋予其另一种新功能,以适应不同的数据类型。
注:
- 对于内置的数据类型的表达式运算符是不可能改变的,如1+1 = 5是不会发生的
- 不要滥用运算符重载
7.1、加号运算符重载
实现两个自定义数据类型相加的运算。通过成员函数,实现两个对象相加属性后返回新的对象。
如:Person p3 = p1 + p2; // p1 与 p2 也是Person类型对象
(1)成员函数实现加号运算符重载
#include <iostream>
using namespace std;
class Person
{
public:
int a;
int b;
public:
Person()
{
}
Person(int _a, int _b)
{
this->a = _a;
this->b = _b;
}
//成员函数实现加号运算符重载
Person operator+(Person& p)
{
Person tmp;
tmp.a = this->a + p.a;
tmp.b = this->b + p.b;
return tmp;
}
void print()
{
cout << "a = " << a << ", b = " << b << endl;
}
};
int main()
{
//类和对象 - 运算符重载 - 加号运算符重载
Person p1(1, 1);
Person p2(2, 5);
Person p3 = p1 + p2; //本质上是Person p3 = p1.operator+(p2);
Person p4 = p1.operator+(p2);
p3.print();
p4.print();
system("pause");
return 0;
}
输出结果
a = 3, b = 6
a = 3, b = 6
(2)全局函数实现加号运算符重载
运算符重载也可以发生函数重载
#include <iostream>
using namespace std;
class Person
{
public:
int a;
int b;
public:
Person()
{
}
Person(int _a, int _b)
{
this->a = _a;
this->b = _b;
}
void print()
{
cout << "a = " << a << ", b = " << b << endl;
}
};
//全局函数实现加号运算符重载
Person operator+(Person& p1, Person& p2)
{
Person tmp;
tmp.a = p1.a + p2.a;
tmp.b = p1.b + p2.b;
return tmp;
}
//全局函数实现加号运算符重载,函数重载
Person operator+(Person& p1,int num)
{
Person tmp;
tmp.a = p1.a + num;
tmp.b = p1.b + num;
return tmp;
}
int main()
{
//类和对象 - 运算符重载 - 全局函数实现加号运算符重载
Person p1(1, 3);
Person p2(2, 5);
Person p3 = p1 + p2; //本质上是Person p3 = operator+(p1, p2);
Person p4 = operator+(p1, p2);
p3.print();
p4.print();
//运算符重载 - 函数重载
Person p5 = p1 + 10; //本质上是Person p5 = operator+(p1, 10);
Person p6 = operator+(p1, 10);
p5.print();
system("pause");
return 0;
}
输出结果
a = 3, b = 8
a = 3, b = 8
a = 11, b = 13
7.2、左移运算符重载
可以输出自定义数据类型。左移运算符重载只能通过全局函数实现,不能使用成员函数实现。
如 cout << p; // p为Person类型对象
#include <iostream>
using namespace std;
class Person
{
//全局函数作友元访问私有成员
friend ostream& operator<<(ostream& cout, Person& p);
private:
int a;
int b;
public:
Person(int _a, int _b)
{
this->a = _a;
this->b = _b;
}
//成员函数实现左移运算符重载,实际调用p.operator<<(cout) ,简化写法 p << cout
//不能让cout在左边写成cout << p形式,因此不能使用成员函数实现左移运算符重载
//void operator<<(ostream& cout)
//{
//}
void print()
{
cout << "a = " << a << ", b = " << b << endl;
}
};
//只能使用全局函数实现左移运算符重载
ostream& operator<<(ostream& cout, Person& p)
{
//成员是私有权限,可以通过友元访问
cout << "a = " << p.a << ", b = " << p.b << endl;
return cout;
}
int main()
{
//类和对象 - 运算符重载 - 全局函数实现左移运算符重载
Person p(5, 3);
cout << p; //本质上是operator<<(cout, p)
operator<<(cout, p);
cout << "可以连载<<运算符 " << p << endl;
system("pause");
return 0;
}
输出结果
a = 5, b = 3
a = 5, b = 3
可以连载<<运算符 a = 5, b = 3
7.3、递增运算符重载
实现前置递增与后置递增 ++a; 与 a++;
前置递增返回引用,后置递增返回值
(1)前置递增
#include <iostream>
using namespace std;
class MyInteger
{
//全局函数作友元
friend ostream& operator<<(ostream& cout, MyInteger& myint);
private:
int a;
public:
MyInteger(int _a) {
a = _a;
}
//前置递增运算符重载 - 前置递增返回引用
MyInteger& operator++()
{
a++;
return *this;
}
};
//全局函数实现左移运算符重载
ostream& operator<<(ostream& cout, MyInteger& myint)
{
//通过友元访问私有成员
cout << myint.a << endl;
return cout;
}
int main()
{
//类和对象 - 运算符重载 -递增运算符重载 - 前置递增
MyInteger myint(1);
//前置递增:先递增再返回结果
cout << "前置递增结果:" << ++(++myint) << endl;
system("pause");
return 0;
}
输出结果
前置递增结果:3
(2)后置递增
#include <iostream>
using namespace std;
class MyInteger
{
//全局函数作友元
friend ostream& operator<<(ostream& cout, MyInteger myint);
private:
int a;
public:
MyInteger(int _a) {
a = _a;
}
//前置递增运算符重载
MyInteger& operator++()
{
a++;
return *this;
}
//后置递增运算符重载,通过使用函数占位参数int实现函数重载,标识为后置递增,区分前置递增
MyInteger operator++(int)
{
MyInteger tmp = *this; //先返回(先记录返回值)
a++; //再递增
return tmp;
}
};
//全局函数实现左移运算符重载
ostream& operator<<(ostream& cout, MyInteger myint)
{
//通过友元访问私有成员
cout << myint.a << endl;
return cout;
}
int main()
{
//类和对象 - 运算符重载 -递增运算符重载 - 后置递增
MyInteger myint(1);
//后置递增:先返回结果再递增
cout << myint++ << endl;
cout << "后置递增结果:" << myint << endl;
system("pause");
return 0;
}
输出结果
1
后置递增结果:2
7.4、赋值运算符重载
C++编译器至少给一个类添加4个函数:
- 默认构造函数 - 无参,函数体为空
- 默认析构函数 - 无参,函数体为空
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=,对属性进行值拷贝
如果类中有属性指向堆区,赋值操作时会出现浅拷贝问题(多次释放空间),此问题在学习拷贝构造函数的深拷贝和浅拷贝时也遇到,可参见:C++ 学习(11)类和对象、封装、访问权限、成员属性私有性、构造函数与析构函数_瘦身小蚂蚁的博客-CSDN博客
实现如 p2 = p1; //p1, p2为类对象
#include <iostream>
using namespace std;
class Person
{
public:
int* age;
Person(int _age)
{
age = new int(_age);
}
//赋值运算符重载
Person& operator=(Person& p)
{
//先清除再创建
if (age != NULL)
{
delete age;
age = NULL;
}
//age = p.age; //浅拷贝,使用浅拷贝会抛异常
//深拷贝
age = new int(*p.age);
return *this;
}
~Person()
{
//释放堆空间
if (age != NULL)
{
delete age;
age = NULL;
}
}
};
int main()
{
//类和对象 - 运算符重载 - 赋值运算符重载
Person p1(2);
Person p2(3);
Person p3(5);
p2 = p1;
cout << *p2.age << endl;
p3 = p2 = p1;
cout << *p3.age << endl;
system("pause");
return 0;
}
输出结果
2
2
7.5、关系运算符重载
可以让两个自定义类型对象进行比较操作。
实现如 p2 != p1; //p1, p2为类对象
#include <iostream>
using namespace std;
class Person
{
public:
string name;
int age;
Person(string _name, int _age) : name(_name), age(_age)
{
}
//关系运算符重载 - ==
bool operator==(Person& p)
{
if (name == p.name && age == p.age)
{
return true;
}
return false;
}
//关系运算符重载 - !=
bool operator!=(Person& p)
{
if (name == p.name && age == p.age)
{
return false;
}
return true;
}
};
int main()
{
//类和对象 - 运算符重载 - 关系运算符重载
Person p1("Tracy", 20);
Person p2("Tracy", 20);
if (p1 == p2)
{
cout << "相等" << endl;
}
else
{
cout << "不相等" << endl;
}
if (p1 != p2)
{
cout << "不相等" << endl;
}
else
{
cout << "相等" << endl;
}
system("pause");
return 0;
}
输出结果
相等
相等
7.6、函数调用运算符重载
函数调用运算符()重载,由于重载后使用的方式非常像函数调用,因此也称为仿函数;仿函数没有固定写法,非常灵活。
#include <iostream>
using namespace std;
class Person
{
public:
//函数调用运算符重载
int operator()(int a, int b)
{
return a + b;
}
};
class Person2
{
public:
void operator()(string str)
{
cout << str << endl;
}
};
int main()
{
//类和对象 - 运算符重载 - 函数调用运算符重载,也称为仿函数
Person p1;
cout << p1(3, 5) << endl;
//匿名函数对象,Person()为警钟函数对象
cout << Person()(7, 9) << endl;
Person2 p2;
p2("Hello, Tracy!");
system("pause");
return 0;
}
输出结果
8
16
Hello, Tracy!