0%

C/C++ 填坑 static的使用

我对C++面向对象部分的内容一直没有很好的理解,今天正好遇到了相关的问题,就学习一下,补补坑。

1. 问题的产生

遇到这个问题是因为刷一道简单题的时候遇到了不明所以的编译错误。
解题方案试图在Solution类中使用自己定义的比较函数调用std::sort对vector>进行排序。

1
class Solution {
2
protected:
3
    bool cmp(const vector<int>&a,const vector<int>&b) const{
4
        return a[0]<b[0];
5
    }
6
public:
7
    void merge(vector<vector<int>>& a) {
8
        sort(a.begin(),a.end(),cmp);
9
//        ...
10
    }
11
};

2. 问题的解决

事实上,以下代码都是能通过编译的。

1
class Solution {
2
public:
3
    void merge(vector<vector<int>>& a) {
4
        sort(a.begin(),a.end(),[](vector<int>&a,vector<int>&b){
5
             return a[0]<b[0]||(a[0]==b[0]&&a[1]<b[1]);
6
        });
7
//        ...
8
    }
9
};
1
class Solution {
2
protected:
3
    struct cmp{
4
        bool operator()(const vector<int>&a,const vector<int>&b) const{
5
            return a[0]<b[0];
6
        }
7
    };
8
public:
9
    void merge(vector<vector<int>>& a) {
10
        sort(a.begin(),a.end(),cmp());
11
//        ...
12
    }
13
};
1
class Solution {
2
protected:
3
    static bool cmp(const vector<int> &a,const vector<int> &b){
4
        return a[0]<b[0];
5
    }
6
public:
7
    void merge(vector<vector<int>>& a) {
8
        sort(a.begin(),a.end(),cmp);
9
//        ...
10
    }
11
};

3. static的用法

前两种方式分别利用lambda表达式和cmp::operator()解决的。其实这两个解决方案也都有各自深奥之处,我也只是知其然而不知其所以然(递归挖坑QAQ)。
网上不少博客也只是说std::sort的使用方法而已,研究第三个参数原理的人寥寥无几,或许学C++还是要多看cppreference
这里主要填一下第三种方法的坑,即static的用法。
C/C++的static修饰词可以用在以下地方:

  • 全局变量(面向过程)
  • 局部变量(面向过程)
  • 函数(面向过程)
  • 类内成员(面向对象)
  • 类内成员函数(面向对象)

3.1 静态全局变量

在全局变量前,加上关键字static,该变量就被定义成为一个静态全局变量。

1
static int n;

静态全局变量的特点:

  • 该变量在全局数据区分配内存;
  • 未经初始化的静态全局变量会被程序自动初始化为0(自动变量的自动初始化值是随机的);
  • 静态全局变量在声明它的整个文件都是可见的,而在文件之外是不可见的;  
  • 静态变量都在全局数据区分配内存,包括后面将要提到的静态局部变量。对于一个完整的程序,在内存中的分布情况如下:【代码区】【全局数据区】【堆区】【栈区】,一般程序的由new产生的动态数据存放在堆区,函数内部的自动变量存放在栈区,静态数据(即使是函数内部的静态局部变量)存放在全局数据区。自动变量一般会随着函数的退出而释放空间,而全局数据区的数据并不会因为函数的退出而释放空间。

也就是说,若使用static修饰变量n,对于另一个文件,如果试图使用:

1
extern int n;

是无法编译通过的。

有了这一部分的铺垫,下面两部分只要类比一下就可以理解了。

3.2 静态局部变量

静态局部变量的特点:

  • 静态局部变量在全局数据区分配内存;
  • 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
  • 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
  • 静态局部变量始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

例如执行以下代码会得到结果1,2,3。

1
void test(){
2
    static int n;
3
    cout<<n++<<endl;
4
}
5
6
int main(){
7
    test();test();test();
8
    return 0;
9
}

3.3 静态函数

静态函数类似于静态全局变量,主要用于保证自身在其他文件中不可用。

3.4 静态成员变量

类比以上概念,可以类比出静态成员变量的特点:

  • 静态成员变量是该类的所有对象所共有的。对于普通成员变量,每个类对象都有自己的一份拷贝。而静态成员变量一共就一份,无论这个类的对象被定义了多少个,静态成员变量只分配一次内存,由该类的所有对象共享访问。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
  • 因为静态数据成员在全局数据区分配内存,由本类的所有对象共享,所以,它不属于特定的类对象,不占用对象的内存,而是在所有对象之外开辟内存,在没有产生类对象时其作用域就可见。因此,在没有类的实例存在时,静态成员变量就已经存在,我们就可以操作它;
  • 静态成员变量存储在全局数据区。static成员变量的内存空间既不是在声明类时分配,也不是在创建对象时分配,而是在初始化时分配。静态成员变量必须初始化,而且只能在类体外进行。否则,编译能通过,链接不能通过。初始化时可以赋初值,也可以不赋值。如果不赋值,那么会被默认初始化,一般是 0。静态数据区的变量都有默认的初始值,而动态数据区(堆区、栈区)的变量默认是垃圾值。
  • static成员变量和普通static变量一样,编译时在静态数据区分配内存,到程序结束时才释放。这就意味着,static成员变量不随对象的创建而分配内存,也不随对象的销毁而释放内存。而普通成员变量在对象创建时分配内存,在对象销毁时释放内存。
  • sizeof 运算符不会计算静态成员变量。

如果需要访问静态成员变量,可以使用<类对象名>.<静态数据成员名>或者<类类型名>::<静态数据成员名>

静态成员变量的一个很好的应用有:股票类的交易利率。

3.5 静态成员函数

首先要回顾的一点就是,普通的成员函数一般都隐含了一个this指针。比如如下代码:

1
class TEST{
2
protected:
3
    int magic_number;
4
public:
5
    void test(){
6
        cout<<magic_number<<endl;
7
    }
8
};

事实上编译器会将test()函数的补充为void(TEST *this),而这也是this->magic_number的由来。
读到这里,相信我们已经可以理解为什么之前的代码会产生编译错误了。
那么静态成员函数在做什么呢?与普通函数相比,静态成员函数属于类本身,而不作用于对象,因此它不具有this指针。正因为它没有指向某一个对象,所以它无法访问属于类对象的非静态成员变量和非静态成员函数,它只能调用其余的静态成员函数和静态成员变量。从另一个角度来看,由于静态成员函数和静态成员变量在类实例化之前就已经存在可以访问,而此时非静态成员还是不存在的,因此静态成员不能访问非静态成员。

静态成员函数的特点:

  • 出现在类体外的函数定义不能指定关键字static;
  • 静态成员之间可以相互访问,即静态成员函数(仅)可以访问静态成员变量、静态成员函数;
  • 静态成员函数不能访问非静态成员函数和非静态成员变量;
  • 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
  • 由于没有this指针的额外开销,静态成员函数与类的全局函数相比速度上会稍快;

调用静态成员函数,两种方式:实例对象使用.操作符或者直接使用<类名>::<静态成员函数名>(<参数表>)

4 题解

最后贴一下题解吧,虽然是道简单题,但只要有心,就会有意外收获。

1
class Solution {
2
public:
3
    vector<vector<int>> merge(vector<vector<int>>& a) {
4
        if (a.size()==0) return {};
5
        sort(a.begin(),a.end(),[](vector<int>&a,vector<int>&b){
6
             return a[0]<b[0];
7
        });
8
        vector<int> cur={a[0]};
9
        vector<vector<int>> ret;
10
        for (auto &it:a){
11
            if (it[0]<=cur[1]){
12
                cur[1]=max(cur[1],it[1]);
13
            }
14
            else{
15
                ret.push_back(cur);
16
                cur=it;
17
            }
18
        }
19
        ret.push_back(cur);
20
        return ret;
21
    }
22
};

部分内容参考