类和对象
类用于指定对象的形式,是一种用户自定义的数据类型,它是一种封装了数据和函数的组合。
类中的数据称为成员变量,函数称为成员函数。类可以被看作是一种模板,可以用来创建具有相同属性和行为的多个对象。
一、定义类
类的定义需要使用关键字class,然后指定类的名称,类的主体部分在花括号中,主体包含类的成员变量和成员函数。
定义一个类,本质上是定义一个数据类型的蓝图,它定义了类的对象包括了什么,以及可以在这个对象上执行哪些操作。
class myclass
{
Access specifilers: //访问修饰符:private/public/protected 三种标识符
Date members/variables; //变量
Member functions(){} //成员函数
}; //结束一个类
例子:使用关键字 class 定义Box数据类型,包含了三个成员变量 length、breadth 和 height
class Box
{
public:
double length;//盒子的长
double breadth;//盒子的宽
double height;//盒子的高
};
二、定义对象
类提供了对象的蓝图,所以基本上,对象是根据类来创建的。声明类的对象,就像声明基本类型的变量一样。
对象是拥有相同属性和行为(函数)的类的实例化结果。
**例子:**声明类 Box 的两个对象,Box1和Box2
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
三、访问数据成员
类的对象的**公共数据成员(public)**可以使用直接成员访问运算符 . 来访问。**私有的成员和受保护的成员(private & protected)**不能使用直接成员访问运算符 *.*来直接访问
例子:
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double get(void);
void set( double len, double bre, double hei );
};
// 成员函数定义
double Box::get(void)
{
return length * breadth * height;
}
void Box::set( double len, double bre, double hei)
{
length = len;
breadth = bre;
height = hei;
}
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
Box Box3; // 声明 Box3,类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.height = 5.0;
Box1.length = 6.0;
Box1.breadth = 7.0;
// box 2 详述
Box2.height = 10.0;
Box2.length = 12.0;
Box2.breadth = 13.0;
// box 1 的体积
volume = Box1.height * Box1.length * Box1.breadth;
cout << "Box1 的体积:" << volume <<endl;
// box 2 的体积
volume = Box2.height * Box2.length * Box2.breadth;
cout << "Box2 的体积:" << volume <<endl;
// box 3 详述
Box3.set(16.0, 8.0, 12.0);
volume = Box3.get();
cout << "Box3 的体积:" << volume <<endl;
return 0;
}
//结果
Box1 的体积:210
Box2 的体积:1560
Box3 的体积:1536
四、类成员函数
类的成员函数是指那些把定义和原型写在类定义内部的函数,就像类定义中的其他变量一样。
类成员函数是类的一个成员,它可以操作类的任意对象,可以访问对象中的所有成员。
之前定义的类 Box,现在我们要使用成员函数来访问类的成员,而不是直接访问这些类的成员。
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
double getVolume(void)
{
return length * breadth * height;
}
};
成员函数可以定义在类定义内部,或者单独使用范围解析运算符 :: 来定义。也可以在类的外部使用范围解析运算符**:😗* 定义该函数
double Box::getVolume(void)
{
return length * breadth * height;
}
上述例子就是在类的外部进行定义。
在这里,需要强调一点,在**:: **运算符之前必须使用类名。
调用成员函数是在对象上使用点运算符**.**,这样它就能操作与该对象相关的数据。
Box myBox; // 创建一个对象
myBox.getVolume(); // 调用该对象的成员函数
**例子:**将上述例子改成使用成员函数的形式
#include <iostream>
using namespace std;
class Box
{
public:
double length; // 长度
double breadth; // 宽度
double height; // 高度
// 成员函数声明
double getVolume(void);
void setLength( double len );
void setBreadth( double bre );
void setHeight( double hei );
};
// 成员函数定义
double Box::getVolume(void)
{
return length * breadth * height;
}
void Box::setLength( double len )
{
length = len;
}
void Box::setBreadth( double bre )
{
breadth = bre;
}
void Box::setHeight( double hei )
{
height = hei;
}
// 程序的主函数
int main( )
{
Box Box1; // 声明 Box1,类型为 Box
Box Box2; // 声明 Box2,类型为 Box
double volume = 0.0; // 用于存储体积
// box 1 详述
Box1.setLength(6.0);
Box1.setBreadth(7.0);
Box1.setHeight(5.0);
// box 2 详述
Box2.setLength(12.0);
Box2.setBreadth(13.0);
Box2.setHeight(10.0);
// box 1 的体积
volume = Box1.getVolume();
cout << "Box1 的体积:" << volume <<endl;
// box 2 的体积
volume = Box2.getVolume();
cout << "Box2 的体积:" << volume <<endl;
return 0;
}
//结果
Box1 的体积: 210
Box2 的体积: 1560
五、类访问修饰符
数据封装是面向对象编程的一个重要特点,它防止函数直接访问类类型的内部成员。
类成员的访问限制是通过在类主体内部对各个区域标记 public、private、protected 来指定的。
关键字 public、private、protected 称为访问修饰符。
一个类可以有多个 **public、protected 或private **标记区域。
每个标记区域在下一个标记区域开始之前或者在遇到类主体结束右括号之前都是有效的。
成员和类的默认访问修饰符是 private。
class Base {
public:
// 公有成员
protected:
// 受保护成员
private:
// 私有成员
};
5.1 共有(public)成员
公有成员在程序中类的外部是可访问的。
可以不使用任何成员函数来设置和获取公有变量的值。
#include <iostream>
using namespace std;
class Line
{
public:
double length;
void setLength( double len );
double getLength( void );
};
// 成员函数定义
double Line::getLength(void)
{
return length ;
}
void Line::setLength( double len )
{
length = len;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
// 不使用成员函数设置长度
line.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of line : " << line.length <<endl;
return 0;
}
//结果
Length of line : 6
Length of line : 10
对象中的公有成员是整个类中的可以随便引用、修改的量。不同的对象修改公有对象不影响类的公有成员。
5.2 私有(private)成员
私有成员变量或函数在类的外部是不可访问的,甚至是不可查看的。
只有类和友元函数可以访问私有成员。
默认情况下,类的所有成员都是私有的。
在下面的类中,width 是一个私有成员.这意味着,如果没有使用任何访问修饰符,类的成员将被假定为私有成员。
class Box
{
double width;//没使用任何标识符,会定义为私有成员
public:
double length;
void setWidth( double wid );
double getWidth( void );
};
我们一般会在私有区域定义数据,在公有区域定义相关的函数,以便在类的外部也可以调用这些函数。
#include <iostream>
using namespace std;
class Box
{
public:
double length;
void setWidth( double wid );
double getWidth( void );
private:
double width;
};
// 成员函数定义
double Box::getWidth(void)
{
return width ;
}
void Box::setWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
Box box;
// 不使用成员函数设置长度
box.length = 10.0; // OK: 因为 length 是公有的
cout << "Length of box : " << box.length <<endl;
// 不使用成员函数设置宽度
// box.width = 10.0; // Error: 因为 width 是私有的。外部是无法直接定义的。
box.setWidth(10.0); // 可以使用成员函数设置宽度
cout << "Width of box : " << box.getWidth() <<endl;
return 0;
}
//结果
Length of box : 10
Width of box : 10
5.3 受保护(protected)成员
受保护(protected)成员变量或函数与私有成员十分相似,但有一点不同,受保护(protected)成员在派生类(即子类)中是可访问的。
下面的实例中,我们从父类 Box 派生了一个子类 smallBox。下面的实例与前面的实例类似,在这里 width 成员可被派生类 **smallBox **的任何成员函数访问。
#include <iostream>
using namespace std;
class Box
{
protected:
double width;
};
class SmallBox:Box // SmallBox 是派生类
{
public:
void setSmallWidth( double wid );
double getSmallWidth( void );
};
// 子类的成员函数
double SmallBox::getSmallWidth(void)
{
return width ;
}
void SmallBox::setSmallWidth( double wid )
{
width = wid;
}
// 程序的主函数
int main( )
{
SmallBox box;
// 使用成员函数设置宽度
box.setSmallWidth(5.0);
cout << "Width of box : "<< box.getSmallWidth() << endl;
return 0;
}
//结果
Width of box : 5
5.4 类访问修饰符在继承中的特点
有public, protected, private三种继承方式,它们相应地改变了基类成员的访问属性。
- 1.**public 继承:**基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:public, protected, private
- 2.**protected 继承:**基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:protected, protected, private
- 3.**private 继承:**基类 public 成员,protected 成员,private 成员的访问属性在派生类中分别变成:private, private, private
但无论哪种继承方式,下面两点都没有改变:
- 1.private 成员只能被本类成员(类内)和友元访问,不能被派生类访问;
- 2.protected 成员可以被派生类访问。
public < protected < private
私密性强的会自然吞噬掉私密性弱的
**例子:**public继承
#include<iostream>
#include<assert.h>
using namespace std;
class A{
public:
int a;
A(){
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun(){
cout << a << endl; //正确
cout << a1 << endl; //正确
cout << a2 << endl; //正确
cout << a3 << endl; //正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : public A{
public:
int a;
B(int i){
A();
a = i;
}
void fun(){
cout << a << endl; //正确,public成员
cout << a1 << endl; //正确,基类的public成员,在派生类中仍是public成员。
cout << a2 << endl; //正确,基类的protected成员,在派生类中仍是protected可以被派生类访问。
cout << a3 << endl; //错误,基类的private成员不能被派生类访问。
}
};
int main(){
B b(10);
cout << b.a << endl;
cout << b.a1 << endl; //正确
cout << b.a2 << endl; //错误,类外不能访问protected成员
cout << b.a3 << endl; //错误,类外不能访问private成员
system("pause");
return 0;
}
**例子:**protected继承
#include<iostream>
#include<assert.h>
using namespace std;
class A{
public:
int a;
A(){
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun(){
cout << a << endl; //正确
cout << a1 << endl; //正确
cout << a2 << endl; //正确
cout << a3 << endl; //正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : protected A{
public:
int a;
B(int i){
A();
a = i;
}
void fun(){
cout << a << endl; //正确,public成员。
cout << a1 << endl; //正确,基类的public成员,在派生类中变成了protected,可以被派生类访问。
cout << a2 << endl; //正确,基类的protected成员,在派生类中还是protected,可以被派生类访问。
cout << a3 << endl; //错误,基类的private成员不能被派生类访问。
}
};
int main(){
B b(10);
cout << b.a << endl; //正确。public成员
cout << b.a1 << endl; //错误,protected成员不能在类外访问。
cout << b.a2 << endl; //错误,protected成员不能在类外访问。
cout << b.a3 << endl; //错误,private成员不能在类外访问。
system("pause");
return 0;
}
**例子:**private继承
#include<iostream>
#include<assert.h>
using namespace std;
class A{
public:
int a;
A(){
a1 = 1;
a2 = 2;
a3 = 3;
a = 4;
}
void fun(){
cout << a << endl; //正确
cout << a1 << endl; //正确
cout << a2 << endl; //正确
cout << a3 << endl; //正确
}
public:
int a1;
protected:
int a2;
private:
int a3;
};
class B : private A{
public:
int a;
B(int i){
A();
a = i;
}
void fun(){
cout << a << endl; //正确,public成员。
cout << a1 << endl; //正确,基类public成员,在派生类中变成了private,可以被派生类访问。
cout << a2 << endl; //正确,基类的protected成员,在派生类中变成了private,可以被派生类访问。
cout << a3 << endl; //错误,基类的private成员不能被派生类访问。
}
};
int main(){
B b(10);
cout << b.a << endl; //正确。public成员
cout << b.a1 << endl; //错误,private成员不能在类外访问。
cout << b.a2 << endl; //错误, private成员不能在类外访问。
cout << b.a3 << endl; //错误,private成员不能在类外访问。
system("pause");
return 0;
}
六、类构造函数&析构函数
6.1 类的构造函数
类的构造函数是类的一种特殊的成员函数,它会在每次创建类的新对象时执行。
构造函数的名称与类的名称是完全相同的,并且不会返回任何类型,也不会返回 void。
构造函数可用于为某些成员变量设置初始值。
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数(类似于初始化的作用)
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
6.2 带参数的构造函数
默认的构造函数没有任何参数,但如果需要,构造函数也可以带有参数。这样在创建对象时就会给对象赋初始值
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(double len); // 这是构造函数
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line( double len)
{
cout << "Object is being created, length = " << len << endl;
length = len;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line(10.0);
// 获取默认设置的长度
cout << "Length of line : " << line.getLength() <<endl;
// 再次设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
//结果
Object is being created, length = 10
Length of line : 10
Length of line : 6
6.3 使用初始化列表来初始化字段
使用初始化列表的原因
初始化类的成员有两种方式,一是使用初始化列表,二是在构造函数体内进行赋值操作。
主要是性能问题,对于内置类型,如int, float等,使用初始化类表和在构造函数体内初始化差别不是很大。
但是对于类类型来说,最好使用初始化列表,使用初始化列表会少一次调用默认构造函数的过程,这对于数据密集型的类来说,是非常高效的。
使用初始化列表来初始化字段
Line::Line( double len): length(len)
{
cout << "Object is being created, length = " << len << endl;
}
//两种语法是等同的
Line::Line( double len)
{
length = len;
cout << "Object is being created, length = " << len << endl;
}
假设有一个类 C,具有多个字段 X、Y、Z 等需要进行初始化,同理地,可以使用上面的语法,只需要在不同的字段使用逗号进行分隔
C::C( double a, double b, double c): X(a), Y(b), Z(c)
{
....
}
例子:
class Person {
public:
int m_A;
int m_B;
int m_C;
////传统方式初始化
//Person(int a, int b, int c) {
// m_A = a;
// m_B = b;
// m_C = c;
//}
//初始化列表方式初始化
Person(int a, int b, int c) :m_A(a), m_B(b), m_C(c) {}
void PrintPerson() {
cout << "m_A:" << m_A << endl;
cout << "m_B:" << m_B << endl;
cout << "m_C:" << m_C << endl;
}
};
int main() {
Person p(1, 2, 3);
p.PrintPerson();
system("pause");
return 0;
}
6.4 类的析构函数
类的析构函数是类的一种特殊的成员函数,它会在每次删除所创建的对象时执行。
析构函数的名称与类的名称是完全相同的,只是在前面加了个波浪号**(~)作为前缀,它不会返回任何值,也不能带有任何参数。析构函数有助于在跳出程序**(比如关闭文件、释放内存等)前释放资源。
#include <iostream>
using namespace std;
class Line
{
public:
void setLength( double len );
double getLength( void );
Line(); // 这是构造函数声明
~Line(); // 这是析构函数声明
private:
double length;
};
// 成员函数定义,包括构造函数
Line::Line(void)
{
cout << "Object is being created" << endl;
}
Line::~Line(void)
{
cout << "Object is being deleted" << endl;
}
void Line::setLength( double len )
{
length = len;
}
double Line::getLength( void )
{
return length;
}
// 程序的主函数
int main( )
{
Line line;
// 设置长度
line.setLength(6.0);
cout << "Length of line : " << line.getLength() <<endl;
return 0;
}
6.5、拷贝构造函数
拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新创建的对象。
将上一个对象的初始信息全部接受过来
拷贝构造函数通常用于:
- 通过使用另一个同类型的对象来初始化新创建的对象。
- 复制对象把它作为参数传递给函数。
- 复制对象,并从函数返回这个对象。
- 如果已经存在一个对象,把这个对象再复制一份而且由于拷贝并不需要改变参数,所以参数部分还要用 “const” 来修饰
如果在类中没有定义拷贝构造函数,编译器会自行定义一个。如果类带有指针变量,并有动态内存分配,则它必须有一个拷贝构造函数。
拷贝构造函数的最常见形式
classname (const classname &obj) {
// 构造函数的主体 obj是一个对象,该对象是用于初始化另一个对象的
}
例子:
#include <iostream>
using namespace std;
class Line
{
public:
int getLength( void );
Line( int len ); // 简单的构造函数
Line( const Line &obj); // 拷贝构造函数
~Line(); // 析构函数
private:
int *ptr;
};
// 成员函数定义,包括构造函数
Line::Line(int len)
{
cout << "调用构造函数" << endl;
// 为指针分配内存
ptr = new int;
*ptr = len;
}
Line::Line(const Line &obj)
{
cout << "调用拷贝构造函数并为指针 ptr 分配内存" << endl;
ptr = new int;
*ptr = *obj.ptr; // 拷贝值
}
Line::~Line(void)
{
cout << "释放内存" << endl;
delete ptr;
}
int Line::getLength( void )
{
return *ptr;
}
void display(Line obj)
{
cout << "line 大小 : " << obj.getLength() <<endl;
}
// 程序的主函数
int main( )
{
Line line1(10);
Line line2 = line1; // 这里也调用了拷贝构造函数
//Line Line2(Line1)
display(line1);
display(line2);
return 0;
}
6.6 构造函数的调用规则
默认情况下,c++编译器至少给一个类添加3个函数
1.默认构造函数(无参,函数体为空)
2.默认析构函数(无参,函数体为空)
3.默认拷贝构造函数,对属性进行值拷贝
构造函数调用规则如下:
-
如果用户定义有参构造函数,c++不在提供默认无参构造,但是会提供默认拷贝构造
-
如果用户定义拷贝构造函数,c++不会再提供其他构造函数
#include<iostream> using namespace std; class Student { public: int number; string name; int age; void show() { cout<<"The number of this student is " <<number <<" and the name is "<<name<<". The age is"<<age<<endl; } void set( int num,string nam) { number=num; name=nam; } //带参构造函数 Student(int a) { age=a; cout<<"youcan"<<endl; } ~Student() { cout << "Object is being deleted" << endl; } //拷贝构造函数 Student( const Student &s) { age=s.age; cout<<"copy"<<endl; } }; int main() { Student Stu1(10); Stu1.set(1110 , "yeyushi"); Stu1.show(); Student Stu2(Stu1); Stu2.set(110 , "yehi"); Stu2.show(); system("pause"); return 0; }
6.7 浅拷贝和深拷贝
-
浅拷贝:简单的赋值拷贝操作
-
深拷贝:在堆区重新申请空间,进行拷贝操作
编辑器默认的拷贝构造函数是仅进行简单赋值拷贝操作的浅拷贝,当初始数据里有指针参数时,浅拷贝会直接复制指针的地址。
对于指针来说,当一个对象结束时,会进行析构,把new的堆中的数值释放掉,而你拷贝的新对象会继续使用复制来的地址,使用时可以是NULL,但是当新对象析构时,会重复释放堆区,导致出错。
class Person {
public:
//无参(默认)构造函数
Person() {
cout << "无参构造函数!" << endl;
}
//有参构造函数
Person(int age ,int height) {
cout << "有参构造函数!" << endl;
m_age = age;
m_height = new int(height);
}
//拷贝构造函数
Person(const Person& p) {
cout << "拷贝构造函数!" << endl;
//如果不利用深拷贝在堆区创建新内存,会导致浅拷贝带来的重复释放堆区问题
m_age = p.m_age;
//给拷贝的新对象new一个新地址用于存放数值
m_height = new int(*p.m_height);
}
//析构函数
~Person() {
cout << "析构函数!" << endl;
//释放内存
if (m_height != NULL)
{
delete m_height;
}
}
public:
int m_age;
int* m_height;
};
void test01()
{
Person p1(18, 180);
Person p2(p1);
cout << "p1的年龄: " << p1.m_age << " 身高: " << *p1.m_height << endl;
cout << "p2的年龄: " << p2.m_age << " 身高: " << *p2.m_height << endl;
}
int main() {
test01();
system("pause");
return 0;
}
总结:如果属性有在堆区开辟的,一定要自己提供拷贝构造函数,防止浅拷贝带来的问题
6.8 类对象作为类成员
C++类中的成员可以是另一个类的对象,我们称该成员为 对象成员
在构造时先构造成员里的类
例子:
#include<iostream>
#include<string>
using namespace std;
class Phone{
public:
string PhoneName;
Phone(string name){
PhoneName=name;
cout << "Phone构造" << endl;
}
};
class Person {
public:
Phone M_PhoneName;
string MName;
Person(string PN ,string MN ):M_PhoneName(PN),MName(MN)
{
cout << "Person has building" << endl;
}
void playGame()
{
cout << MName << " use" << M_PhoneName.PhoneName << " phone! " << endl;
}
};
int main() {
Person p("张三" , "苹果X");
p.playGame();
system("pause");
return 0;
}
6.9 类静态成员
我们可以使用 static 关键字来把类成员定义为静态的。当我们声明类的成员为静态时,这意味着无论创建多少个类的对象,静态成员都只有一个副本。
-
静态成员变量
静态成员在类的所有对象中是共享的。如果不存在其他的初始化语句,在创建第一个对象时,所有的静态数据都会被初始化为零。
我们不能把静态成员的初始化放置在类的定义中,但是可以在类的外部通过使用范围解析运算符 :: 来重新声明静态变量从而对它进行初始化
-
所有对象共享同一份数据,当一个对象修改时,其他对象访问的值变为修改后的值
-
在编译阶段分配内存
-
类内声明,类外初始化
-
-
静态成员变量的访问方式
- 通过对象,
p1.m_A
可以直接读出值 - 直接通过类,因为不管是通过哪个对象读取的值都是一样的,所以它可以直接通过类名来读数
Person::m_A
- 私有权限的静态成员变量,我们任然是访问不到的
- 通过对象,
class Person
{
public:
static int m_A; //静态成员变量
//静态成员变量特点:
//1 在编译阶段分配内存
//2 类内声明,类外初始化
//3 所有对象共享同一份数据
private:
static int m_B; //静态成员变量也是有访问权限的
};
int Person::m_A = 10;
int Person::m_B = 10;
void test01()
{
//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.m_A = 100;
cout << "p1.m_A = " << p1.m_A << endl;
Person p2;
p2.m_A = 200;
cout << "p1.m_A = " << p1.m_A << endl; //共享同一份数据
cout << "p2.m_A = " << p2.m_A << endl;
//2、通过类名
cout << "m_A = " << Person::m_A << endl;
//cout << "m_B = " << Person::m_B << endl; //私有权限访问不到
}
int main() {
test01();
system("pause");
return 0;
}
-
静态成员函数
如果把函数成员声明为静态的,就可以把函数与类的任何特定对象独立开来。静态成员函数即使在类对象不存在的情况下也能被调用,静态函数只要使用类名加范围解析运算符 :: 就可以访问。
静态成员函数只能访问静态成员数据、其他静态成员函数和类外部的其他函数。
静态成员函数有一个类范围,他们不能访问类的 this 指针。可以使用静态成员函数来判断类的某些对象是否已被创建。
-
静态成员函数与普通成员函数的区别:
-
静态成员函数没有 this 指针,只能访问静态成员(包括静态成员变量和静态成员函数)。
-
普通成员函数有 this 指针,可以访问类中的任意成员;而静态成员函数没有 this 指针。
-
-
静态成员函数特点
- 所有对象共享同一个函数
- 静态成员函数只能访问静态成员变量
-
静态成员函数的访问方式
-
通过对象,
p1.func();
可以直接读出值 -
直接通过类,因为不管是通过哪个对象函数都是一样的,所以它可以直接通过类名来读数
Person::func();
-
私有权限的静态成员变量,我们是访问不到的
-
class Person
{
public:
//静态成员函数特点:
//1 程序共享一个函数
//2 静态成员函数只能访问静态成员变量
static void func()
{
cout << "func调用" << endl;
m_A = 100;
//m_B = 100; //错误,不可以访问非静态成员变量
}
static int m_A; //静态成员变量
int m_B; //
private:
//静态成员函数也是有访问权限的
static void func2()
{
cout << "func2调用" << endl;
}
};
int Person::m_A = 10;
void test01()
{
//静态成员变量两种访问方式
//1、通过对象
Person p1;
p1.func();
//2、通过类名
Person::func();
//Person::func2(); //私有权限访问不到
}
int main() {
test01();
system("pause");
return 0;
}
七、this指针
7.1成员变量和成员函数是分开存储的
在C++中,类内的成员变量和成员函数分开存储。只有非静态成员变量才属于类的对象上
class Person {
public:
Person() {
mA = 0;
}
//非静态成员变量占对象空间
int mA;
//静态成员变量不占对象空间
static int mB;
//函数也不占对象空间,所有函数共享一个函数实例
void func() {
cout << "mA:" << this->mA << endl;
}
//静态成员函数也不占对象空间
static void sfunc() {
}
};
int main() {
cout << sizeof(Person) << endl;
system("pause");
return 0;
}
如果是空对象,它占用的内存空间为:1
C++编译器会给每一个空对象也分配一个字节的空间,这是为了区分开空对象占内存的位置。不同的对象不能占用同一块内存,所以一个字节进行区分。
7.2 this指针概念
通过上节我们知道在C++中成员变量和成员函数是分开存储的
每一个非静态成员函数只会诞生一份函数实例,也就是说多个同类型的对象会共用一块代码
那么问题是:这一块代码是如何区分那个对象调用自己的呢?
c++通过提供特殊的对象指针,this指针,解决上述问题。this指针指向被调用的成员函数所属的对象
this指针是隐含每一个非静态成员函数内的一种指针
this指针不需要定义,直接使用即可
this 指针是一个特殊的指针,它指向当前对象的实例
每一个对象都能通过 this 指针来访问自己的地址
this指针的用途:
- 当形参和成员变量同名时,可用this指针来区分
- 在类的非静态成员函数中返回对象本身,可使用return *this
class Person
{
public:
Person(int age)
{
//1、当形参和成员变量同名时,可用this指针来区分
this->age = age;
}
Person& PersonAddPerson( const Person &p)
{
this->age += p.age;
//this是指向p2的指针,而*this是指向的就p2这个对象的本体,也就是在地址中的那个对象p2
return *this;
}
//现在PersonAddPerson( const Person &p)这个函数返回的是p2的本体,想要继续和p2有联系就必须用&也就是引用的方式进行返回。这样PersonAddPerson( const Person &p)的返回,就一直是p2了。
//如果仅仅返回p2,不使用引用的方式,则会创建一个新的类p2',再.PersonAddPerson()一次,就会在p2'的基础上再创建一个p2''的新对象。与原先的p2就毫无关联了,链式运算就无法进行。
int age;
};
void test01()
{
Person p1(10);
cout << "p1.age = " << p1.age << endl;
Person p2(10);
p2.PersonAddPerson(p1).PersonAddPerson(p1).PersonAddPerson(p1);
cout << "p2.age = " << p2.age << endl;
}
int main() {
test01();
system("pause");
return 0;
}