静态成员就是在成员变量和成员函数前加上关键字static,静态成员分为:
静态成员变量
- 所有对象共享同一份数据
- 在编译阶段分配内存
- 类内声明,类外初始化
静态成员函数
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量,不能访问非个静态成员变量
访问静态成员方法:
- 通过对象访问:对象名.静态成员
- 通过类名访问: 类名::静态成员
静态成员也受访问权限限制:可以访问public权限的静态成员,不可以访问private的静态成员
#includeusing 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; //程序 }
输出结果
2、C++ 对象模型---- 静态成员变量 ----
通过对象访问静态成员变量, 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()
类内的成员变量和成员函数是分开存储的,只有非静态成员变量才属于类的对象上。
- 非静态成员变量 - 占用对象空间
- 静态成员变量 - 不占用对象空间
- 非静态成员函数 - 不占用对象空间
- 静态成员函数 - 不占用对象空间
#includeusing 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; }
输出结果
3、C++ this指针没有成员类对象占用空间 = 1
有成员对象占用空间(与非静态成员变量有关) = 4
类内的成员变量与成员函数是分开存储的,每一个非静态成员函数只会产生一份函数实例,也就是说多个同类型的对象会共用一块代码。那么这一块代码是如何区分哪个对象调用自己的呢?
C++通过提供特殊的对象指针 - this指针,解决这个问题。
this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this指针的用途
- 当形参和成员变量同名时,可用this指针区分
- 在类的非静态成员函数中返回对象本身,可使用return *this;
#includeusing 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指针 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; }
输出结果
4、C++ 空指针访问成员函数add成员函数返回引用:count = 3
add2成员函数返回值:count = 1
C++中空指针是可以调用成员函数的,但要注意有没有用到this指针,如果成员函数中用到了this指针,需要判断保证代码的健壮性。
#includeusing 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; }
输出结果
5、C++ const修饰成员函数成员函数中不包含this指针
常函数
- 成员函数后加const称为常函数
- 常函数内不可以修改成员属性
- 成员属性声明时加关键字mutable后,在常函数中可以修改
this 的本质
this 的本质是指针常量, 指针常量不可以修改指针的指向,可以修改指针指向的值
this本质:Person* const this
在成员函数后加上const为常函数,即 const Person* const thisconst即修饰指针又修改常量,此时指针的指向不可以修改,指针指向的值也不可以修改
之前学到的引用的本质也是指针常量,关于指针常量可参考:C++ 学习(七)指针、const修饰指针、指针与数组、指针与函数_瘦身小蚂蚁的博客-CSDN博客
常对象
- 声明对象前加const为常对象
- 常对象只能调用常函数
#includeusing namespace std; //类 class Person { public: string name; mutable int age; //构造函数,不加会在运行时创建常对象位置报错 Person() { } //常函数 - 成员函数后加const void show() const { //常函数内不可以修改成员属性 //name = "Tracy"; //修饰成员属性,报错:操作数类型为 const std::string = const char[6] //成员属性声明时加关键字mutable后,在常函数中可以修改 age = 20; } void show2() { age = 27; } }; int main() { //类和对象 - this指针 - const修饰成员函数 Person p1; p1.show(); 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
遇到的问题
6、C++ 友元(friend)不加构造函数,在执行时 const Person p; 报错: p必须初始化const对象,在Person类中加了构造函数后运行正常,目前还不知道为什么会这样?
//构造函数,不加会在运行时创建常对象位置报错
Person() {}
友元是站一个函数或类访问另一个类中的私有成员。关键字为friend。
友元的三种实现
6.1、全局函数作友元
- 全局函数作友元
- 类作友元
- 成员函数作友元
#includeusing 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; }
输出结果
6.2、类作友元全局函数访问对象公有成员:name = Tracy
全局函数访问对象私有成员:phone = 13800000000
#includeusing 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; }
输出结果
6.3、成员函数作友元类对象访问对象公有成员:name = Timo
类对象访问对象私有成员:phone = 13900000000
注意以下类Person与类GoodFriend声明与定义顺序。
#includeusing 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; }
输出结果
7、C++ 运算符重载(operator)成员函数访问对象公有成员:name = Timo
成员函数访问对象私有成员:phone = 13900000000
成员函数访问对象公有成员:name = Timo
对已有的运算符重新定义,赋予其另一种新功能,以适应不同的数据类型。
7.1、加号运算符重载注:
- 对于内置的数据类型的表达式运算符是不可能改变的,如1+1 = 5是不会发生的
- 不要滥用运算符重载
实现两个自定义数据类型相加的运算。通过成员函数,实现两个对象相加属性后返回新的对象。
(1)成员函数实现加号运算符重载如:Person p3 = p1 + p2; // p1 与 p2 也是Person类型对象
#includeusing 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; }
输出结果
(2)全局函数实现加号运算符重载a = 3, b = 6
a = 3, b = 6
运算符重载也可以发生函数重载
#includeusing 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; }
输出结果
7.2、左移运算符重载a = 3, b = 8
a = 3, b = 8
a = 11, b = 13
可以输出自定义数据类型。左移运算符重载只能通过全局函数实现,不能使用成员函数实现。
如 cout << p; // p为Person类型对象
#includeusing 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; }
输出结果
7.3、递增运算符重载a = 5, b = 3
a = 5, b = 3
可以连载<<运算符 a = 5, b = 3
实现前置递增与后置递增 ++a; 与 a++;
(1)前置递增前置递增返回引用,后置递增返回值
#includeusing 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; }
输出结果
(2)后置递增前置递增结果:3
#includeusing 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; }
输出结果
7.4、赋值运算符重载1
后置递增结果:2
C++编译器至少给一个类添加4个函数:
- 默认构造函数 - 无参,函数体为空
- 默认析构函数 - 无参,函数体为空
- 默认拷贝构造函数,对属性进行值拷贝
- 赋值运算符 operator=,对属性进行值拷贝
如果类中有属性指向堆区,赋值操作时会出现浅拷贝问题(多次释放空间),此问题在学习拷贝构造函数的深拷贝和浅拷贝时也遇到,可参见:C++ 学习(11)类和对象、封装、访问权限、成员属性私有性、构造函数与析构函数_瘦身小蚂蚁的博客-CSDN博客
实现如 p2 = p1; //p1, p2为类对象
#includeusing 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; }
输出结果
7.5、关系运算符重载2
2
可以让两个自定义类型对象进行比较操作。
实现如 p2 != p1; //p1, p2为类对象
#includeusing 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、函数调用运算符重载相等
相等
函数调用运算符()重载,由于重载后使用的方式非常像函数调用,因此也称为仿函数;仿函数没有固定写法,非常灵活。
#includeusing 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!



