我们都知道 static 关键字会改变变量的生命周期,其本质是改变变量的存储位置,将变量存放到静态区。
就比如我在函数中声明了一个 static 变量,出了函数作用域这个变量还会存在,当下一次调用函数时直接使用此前定义出的这个变量,而不会再给它开辟一块空间。
那么当用 static 修饰类的成员变量会发生什么呢?
看下面一段代码:
class A {
public:
void Print() {
cout << _n << endl;
}
private:
int _a;
static int _n;
};
int main() {
A aa;
aa.Print();
return 0;
}
报错信息如下:
意思就是链接的时候找不到 _n ,换句话说就是 _n 未定义。
这说明了一点,编译器默认生成的构造函数并没有对静态成员变量进行处理,这里我们可以初步推断 _n 不是对象 aa 的成员。
再看一下 aa 的大小:cout << sizeof(aa) << endl;
实际上,静态成员变量是所有类对象所共有的,不属于任何一个对象。
那静态成员变量怎么定义呢?
在类里面声明一个静态成员变量只是声明,并不是定义。
静态成员变量必须在类外定义,定义时不用添加 static 关键字,但要用限制符说明它是属于哪个类的:
class A {
public:
void Print() {
cout << _n << endl;
}
private:
int _a;
static int _n;
};
int A::_n = 0;
而如果想要访问静态成员变量,有两种方法。
一来可以像它的定义那样直接通过类名::静态成员变量的形式;
二来静态成员变量属于所有对象共有,所有还可以通过对象名.静态变量或者是this->静态变量的形式访问。
那么静态成员可以做什么呢?
举个简单的例子,假如在一段程序中要统计某个类实例化出来的对象的个数,就可以很简单的通过静态变量来实现:
class A {
public:
A() {
++_count;
}
A(const A& t) {
++_count;
}
private:
static int _count;
};
int A::_count = 0;
因为实例化对象时会调用构造函数或拷贝构造函数,所以每调用一次构造函数或拷贝构造函数就让设置好的计数器 +1 即可,计数器用静态变量来实现。
但是还有一个我问你题,我统计出来了对象的个数,但静态变量是私有的,我得到了也没法访问,这时该怎么做呢?
一种方法是设置一个函数int GetACount()
,用匿名对象的方式去访问A().GetACount()
。
虽然这种方式使用起来要比实例化一个具体的对象然后调用要简单,但也还是会调用一次构造函数和析构函数,多多少少有点消耗。
所以有没有更好的方法呢?
静态成员函数与静态成员变量的区别不大,都是声明的时候加一个 static 关键字修饰,只不过一个是函数,一个是变量。
静态成员必须要在类外面初始化,而静态成员函数就没有初始化这一说法,哪有函数初始化的。
静态成员函数作为成员函数,也是可以被所有类实例化出的对象调用,也可以通过匿名对象调用。
但是,静态成员函数是没有隐藏的 this 指针的。
因此,在静态成员函数内部不能直接访问对象的成员变量:
但可以访问静态变量:
和静态成员变量一样,静态成员函数也可以直接通过以类名::静态成员函数的方式访问:
通过这种方式获取到静态变量,没有创造对象,省去了多余的构造和析构。
首先复习一下,const 修饰的成员变量具有只读属性,要在构造函数的初始化列表完成初始化。
既然 const 修饰的变量具有了常量属性,那这个变量存放在哪呢?是常量区吗?
这是一个简单的小误区,const 修饰后的变量叫常变量,本质还是变量,只不过只能读不能写,随着对象的销毁它也会销毁,这一点不要和 static 弄混了。
像 const 修饰成员变量没有什么问题,就一个在初始化列表初始化需要注意的。
那就先看一看下面的代码有什么问题:
class A
{
public:
A(int a = 0) :_a(a) {}
void Print() {
cout << _a << endl;
}
private:
int _a;
};
int main() {
const A aa;
aa.Print();
return 0;
}
这是 const 对象去调用普通成员函数,程序运行起来:
报错了,忽略这里的报错信息。
发生错误的原因在于 Print 函数的参数是 this 指针,指针权限为可读可写,而调用它的对象是 const 只读类型的,只能读不能写,传过去的指针类型是 const A* this
。实参本来是不可修改的,而函数体内却能修改,属于是访问权限放大了,所以会报错。
而解决方法就是将参数 this 指针也变成 const A * 类型,而由于 this 指针作为隐藏参数的特殊性没办法直接加 const,所以C++提供的是这种方法:void Print() const
,后面的 const 就用来修饰 this 指针,在函数体内只读不写。
对于同一个函数,加 const 和不加 const 修饰是可以构成函数重载的:
class A
{
public:
void Print() const {
cout << "void Print() const" << endl;
}
void Print() {
cout << "void Print()" << endl;
}
};
对于只读不写的函数来说,一般要加 const 修饰;
对于需要写的函数来说,不能加 const 修饰;
对于可读可写的函数,例如 operator[] 或 迭代器 iterator 来说,一般要实现加 const 和 不加 const 的两个版本:
对于 const 的使用,总成一句话就是访问权限不能放大。
像 const 对象只能调用 const 成员函数,
而非 const 对象既能调用 const 成员函数又能调用非 const 成员函数。
在 const 成员函数内部也是只能调用其他的 const 成员函数,不能调用非 const ,
非 const 成员函数内部则是通吃。
这是2023.1.12日更新的内容。
上面讲static成员变量时说这种成员变量必须在类外面定义,
这种说法其实是有失偏颇的。
如果是成员变量static const类型呢?
const成员变量可是声明的时候就要初始化啊,
并且不可改变。
所以个人认为这个是C++的一个缺点所在了,
所以static成员变量可以在类里面声明吗?
可以,但仅限于const static。
这是2023.1.14更新的内容。
这个问题是在看一道题的时候发现的问题,
此前一直没有注意。
先看下面一段代码:
int main()
{
const int a = 10;
int* p = (int*)& a;
*p = 20;
printf("a = %d *p = %d\n", a, *p);
return 0;
}
下面是在C语言编译器下的运行结果:
下面是在C++编译器下的运行结果:
C语言中的const就不用多说了,
const了,但没完全const,
而C++看似是优化了这一点。
当碰见const声明时在符号表中放入常量
编译过程中若发现使用常量则直接以符号表中的值替换
编译过程中若发现下述情况则给对应的常量分配存储空间
对const常量使用了extern
对const常量使用&操作符
—— 引用自博客【C++基础入门】2.详解C语言和C++中的const_清风自在 流水潺潺的博客-CSDN博客