深入篇【C++】类与对象:const成员与Static成员
- ⏰<const成员>
- 🕓1.权限
- 🕐2.规则
- 🕒3.思考:
- ⏰<Static成员>
- 🕑1.概念
- 🕗2.特性
- 🕕3.思考:
⏰<const成员>
🕓1.权限
在C语言中,我们知道const是用来缩小权限或者偏移权限的。
//权限的缩小和偏移
int main()
{
const int a = 1;
//const 修饰a表明a不能被修改
//也就是不能通过其他方式来修改变量a,权限由原来的可读可写,变成了可读不可写,权限缩小了。
int& b = a;//不能通过取别名来修改变量a
int* c = &a;//也不能通过指针来修改变量a
const int& b = a;//权限是可以偏移的,相同权限是可以访问的
}
在C++中如果const修饰一个对象会怎么样呢?
class Date
{
public:
Date(int year = 1, int month = 1, int day = 1)
{
_year = year;
_month = month;
_day = day;
}
void Print()
{
cout << _year << "-" << _month << "-" << _day << endl;
}
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2023, 5, 16);
d1.Print();
const Date d2(2023, 5, 17);
d2.Print();
}
而在这里我们发现d1可以调用成员函数Print,而d2无法调用成员函数Print,这是为什么呢?
这里的原因就在于const修饰了对象d2,使得d2的成员无法进行修改了。
我们知道调用成员函数时,会将对象的地址传给函数,函数会用一个this指针来接收。
而被const修饰的d2是不能再通过其他方式来对d2进行访问修改的。所以d2是无法调用Print成员函数的,所以只要cosnt修饰了对象,那么对象就无法再调用成员函数了,因为编译器会自动的将对象的地址传给成员函数,成员函数会用一个this指针来接收。
const使对象d2权限缩小了,由可读可写变成了只读。
那对象d2如何调用它的成员成员函数呢?
我们知道权限缩小后就无法调用,但权限偏移还是可以访问的。所以我们只要让成员函数的权限也变得跟对象d2一样大,那成员函数不就可以调用了吗。所以让const来修饰成员函数就能解决这个问题。
将const西洋参的成员函数称为const成员函数,const修饰类成员函数,实际上修饰的是该成员函数中隐藏的this指针,表明在该成员函数中不能对类的任何成员进行修改。
用const修饰成员函数,成员函数的权限就和对象一样大了,那对象就可以调用成员函数了。
不过这里有一个问题,const修饰成员函数,其实是修饰隐藏的this指针,C++规定this指针不能显式出现在函数外和函数参数列表的。那const应该写在哪里呢?
C++要求const修饰成员函数,const一律写在函数后面。
void Print()const
{
cout << _year << "-" << _month << "-" << _day << endl;
}
//其实本质就是下面这样:
//只不过this指针不能显式出现
void Print(const Date* this)
{
cout << _year << "-" << _month << "-" << _day << endl;
}
在成员函数后面加上const后,普通对象和const修饰的对象都可以调用了
因为成员函数后面加上const后权限变小,普通对象肯定是可以调用的,因为普通对象权限相比较大,const修饰的对象就和const修饰的成员函数权限一样大,所以也可以调用。
那是不是所有的成员函数后面都能加上const修饰呢?
当然不是。
🕐2.规则
const成员规则:
要修改对象成员变量的函数后面是不能加const,一旦加上const那么该函数就不能修改对象成员变量了,但只要成员变量不需要修改的函数后面都能加上const。
所以我们对于那些不需要修改对象成员变量的函数后面都应该加上const,为什么呢?
因为这样普通对象和const修饰的对象都可以调用成员函数。
🕒3.思考:
思考下面几个问题:
1.const对象可以调用非const成员函数吗?
不能,const对象权限小,非const成员函数权限大。
2.非const对象可以调用const成员函数吗?
可以,非const对象权限大,const成员函数权限小。
3.const成员函数内可以调用其他的非const成员函数吗?
不能,cosnt成员函数权限小,其他非const成员函数权限大。
4.非const成员函数内可以调用其他的const成员函数吗?
能,非const成员函数权限大,其他const成员函数权限小。
从权限角度我们可以很轻松的解决这些问题。
⏰<Static成员>
🕑1.概念
声明为static的类成员称为类的静态成员,用static修饰的成员变量,称之为静态成员变量。用static修饰的成员函数,称之为静态成员函数。静态成员变量一定要在类外进行初始化。
有没有一个办法可以计算出一个程序中出现了多少个对象呢?
我们的初步想法是当对象实例化时肯定会调用构造函数,当拷贝生成对象时肯定会调用拷贝构造函数,当对象销毁时肯定会调用析构函数,所以我们只要在这些函数里弄一个计数器就就可以了,当对象调用构造函数或者拷贝构造函数时,计数器加一,当调用析构函数时计数器减一。
而这个计数器要保证在全局都有效,不能是局部变量,不然函数使用完计数器就销毁了,所以我们可以将计数的变量定义为全局变量count,最开始为0.
int _count=0;
class A
{
public:
A() //构造函数
{ ++_count; }
A(const A & t)//拷贝构造
{ ++_count; }
~A() //析构函数
{ --_count; }
private:
int a;
};
void TestA()
{
A a1, a2;//定义了两个对象
cout << _count << endl;//这里应该是2
A a3(a1);//又拷贝一个对象,又多一个计数器应该是3
cout << _count << endl;
}
int main()
{
TestA();
}
这样就可以简单的计算出程序中一共创建了多少对象了。
不过上面的做法有些问题,有哪些问题呢?
计数变量作为全局变量可能会被修改。全局变量的缺点就是谁都可以访问,谁都可以修改。
有没有什么办法可以像C++一样将成员封装起来呢?不让别人随意访问这个变量呢?
C++设计出static成员来解决这个问题。
既想拥有全局范围,又不想被随意访问,只能使用static成员。
static修饰变量,使变量具有静态性,存放在静态区,生命周期跟全局变量一样,都是在程序结束后自动销毁。
而想让该变量不被随意访问,只能作为类成员变量,使用访问限定符private来限定,在类里成员函数是可以随意访问,但在类外无法随意使用。
其实本质上就是将全局变量/静态变量封装。
但C++中有规定:静态成员变量在类里声明,在类外定义。
class A
{
public:
A()
{
++_count;
}
A(const A& t)
{
++_count;
}
~A()
{
--_count;
}
static int GetCount()
{
return _count;
}
private:
static int _count;//静态成员变量在类里面声明
};
int A::_count = 0;//在类外面定义
到这里你可能意识到不对劲了,那计数变量封装在类里面,我们如何得到它呢?
在类外我们是无法使用这个静态变量的。使用域作用符也没有用,域作用符只是告诉这个变量在哪里,但不给你访问能有是用呢。
而想得到这个成员变量,就要有this指针,但哪里来this指针呀。
所以C++又设计出static成员函数来解决这个问题。
用static修饰的成函数称为静态成员函数,而静态成员函数是没有this指针的,只要指定类域和访问限定符就可以访问。
class A
{
public:
static int GetCount()//静态成员函数是没有this指针的,只要指定类域和访问限定符就可以调用
{
return _count;
}
private:
static int _count;
};
int A::_count = 0;
所以我们在类外就可以使用静态成员函数来获得这个成员变量,正常来说我们要想使用类里的成员函数,是需要有this指针的,但静态成员函数是没有this指针,所以它是可以被任意调用的(访问限定符是pubilc)。
class A
{
public:
A()
{
++_count;
}
A(const A& t)
{
++_count;
}
~A()
{
--_count;
}
static int GetCount()
{
return _count;
}
private:
static int _count;
};
int A::_count = 0;
void TestA()
{
A a1, a2;
cout <<A::GetCount()<< endl;
//指定类域和访问限定符就可以调用该函数
A a3(a1);
cout <<A::GetCount()<< endl;
}
int main()
{
TestA();
}
所以一般来说,静态成员和静态成员函数是配套出现的,使用静态成员就要使用静态成员函数。
不过要注意静态成员是存储在静态区的,它不是存储在类里的。并且它不是某个对象特有的成员变量,它是所有对象共享的成员变量,每个对象对这个变量都可以修改,但不能控制其他对象对它修改,所有它是共用的。
这里总结一下静态成员和静态成员函数的特性
🕗2.特性
1.静态成员为所有类对象所共享,不属于某个具体的对象,它是存储在静态区的。
2.静态成员变量必须在类外定义,定义时不添加static关键字,在类里声明,还有静态成员变量不能给缺省值。
3.类静态成员可用 类名::静态成员或者对象.静态成员来访问。
4.静态成员函数没有隐藏的this指针,不能访问任何非静态成员。因为访问其他成需要this指针才可以。
5.静态成员也是类的成员,受访问限定符的限制。
🕕3.思考:
1.静态成员函数可以调用非静态成员函数吗?
不能!
因为如果非静态成员函数需要访问成员变量时,是需要this指针的,但静态成员函数没有this指针,所有无法调用。
2.非静态成员函数可以调用类的静态成员函数吗?
可以的。非静态成员函数有this指针,想干嘛干嘛。
3.如何设计一个类,只能在栈或者堆上创建对象?
int main()
{
C c1;//栈上开辟的
static C c2;//静态区开辟的
C* c3 = new C;//堆上开辟的
}
创建对象都有一个特点那就是调用构造函数,所有我们首先将构造函数进行封闭,放进限定保护符里,不给它们调用那么就无法创建在栈上,在静态区,在堆上创建对象了,但我们要求是只在栈上或者堆上,不能全部无法创建呀。
我们可以这样做,利用成员函数来获得我们想要在哪开辟空间的功能。
class C
{
public:
C Stack()
{
C c1;//栈上开辟的
return c1;
}
C Static()
{
static C c2;//静态区开辟的
return c2;
}
C* Pile()
{
C* c3 = new C;//堆上开辟的
return c3;
}
private:
C()
{ }
};
想在哪创建对象就调用哪个函数,这是我们的想法。
但问题是怎么调用呀?你无法调用成员函数呀。
要调用成员函数那必须要有this指针,也就是创建一个对象才可以调用成员函数,可我们要求就是能给定在哪开辟对象,这里却让我先创建一个对象然后再给定在哪创建对象?
这肯定是不行的,这里就需要用到刚刚的知识了,静态成员函数是没有this指针的,没有this指针也可以调用静态成员函数。
所以这里我们只要将这些成员函数变成静态成员函数,那么我们就可以调用啦。
这样问题就解决了。
class C
{
public:
static C Stack()
{
C c1;//栈上开辟的
return c1;
}
static C Static()
{
static C c2;//静态区开辟的
return c2;
}
static C* Pile()
{
C* c3 = new C;//堆上开辟的
return c3;
}
private:
C()
{ }
};
int main()
{
C c=C::Stack();//栈上开辟的
C* c1 = C::Pile();//堆上开辟的
}