变量

局部变量

普通局部变量是再熟悉不过的变量了, 在任何一个函数内部定义的变量(不加static修饰符)都属于这个范畴。编译器一般不对普通局部变量进行初始化, 也就是说它的值在初始时是不确定的, 除非对其显式赋值。

  • 普通局部变量存储于进程栈空间, 使用完毕会立即释放。

静态局部变量使用static修饰符定义, 即使在声明时未赋初值, 编译器也会把它初始化为0。且静态局部变量存储于进程的全局数据区, 即使函数返回, 它的值也会保持不变。

  • 变量在全局数据区分配内存空间;编译器自动对其初始化
    其作用域为局部作用域, 当定义它的函数结束时, 其作用域随之结束

示例代码如下

#include <iostream>

void fun() {
int num = 10;
std::cout << "num:" << num++ << std::endl;
}

void fun_static() {
static int num = 10;
std::cout << "static num:" << num++ << std::endl;
}

int main () {
fun();
fun_static();
fun();
fun_static();
return 0;
}

运行结果如下

"/mnt/d/Computer/C++ learns/cmake-build-release/Cpp_learns"
num:10
static num:10
num:10
static num:11

Process finished with exit code 0

fun()中, 即便num++下次运行num还是10, 但对于fun_static(), num++会影响到下次运行的结果, 证明当局部变量加上static关键字后, 在函数结束时, 它的值也保持不变, 并不会被释放

全局变量

全局变量定义在函数体外部, 在全局数据区分配存储空间, 且编译器会自动对其初始化。

普通全局变量对整个工程可见, 其他文件可以使用extern外部声明后直接使用。也就是说其他文件不能再定义一个与其相同名字的变量了(否则编译器会认为它们是同一个变量)。

静态全局变量仅对当前文件可见, 其他文件不可访问, 其他文件可以定义与其同名的变量, 两者互不影响。

  • 在定义不需要与其他文件共享的全局变量时, 加上static关键字能够有效地降低程序模块之间的耦合, 避免不同文件同名变量的冲突, 且不会误使用。

函数

函数的使用方式与全局变量类似, 在函数的返回类型前加上static, 就是静态函数。其特性如下:

  • 静态函数只能在声明它的文件中可见, 其他文件不能引用该函数

  • 不同的文件可以使用相同名字的静态函数, 互不影响

  • 非静态函数可以在另一个文件中直接引用, 甚至不必使用extern声明

下面两个文件的例子说明不使用static声明的函数文件互相影响

/* file.cpp */
#include <iostream>

static void fun() {
std::cout << "hello from fun" << std::endl;
}

void fun1() {
std::cout << "hello from fun1" << std::endl;
}

int main() {
fun();
fun1();
return 0;
}

/* file2.c */
#include <iostream>

void fun1() {
std::cout << "hello from static fun1" << std::endl;
}

运行结果

/tmp/cc9s5Fnk.o: In function `fun1()':
file2.cpp:(.text+0x0): multiple definition of `fun1()'
/tmp/ccQcjpLK.o:file.cpp:(.text+0x2f): first defined here
collect2: error: ld returned 1 exit status

修改文件

/* file.cpp */
#include <iostream>

static void fun() {
std::cout << "hello from fun" << std::endl;
}

void fun1() {
std::cout << "hello from fun1" << std::endl;
}

int main() {
fun();
fun1();
return 0;
}

/* file2.c */
#include <iostream>

static void fun1() {
std::cout << "hello from static fun1" << std::endl;
}

运行结果

➜  Desktop g++ file.cpp file2.cpp
➜ Desktop ./a.out
hello from fun
hello from fun1

静态数据成员

在类内数据成员的声明前加上关键字static, 该数据成员就是类内的静态数据成员。先举一个静态数据成员的例子。

#include <iostream>

class fun {
public:
fun(int a, int b, int c);

static void GetSum();

private:
static int Sum;//声明静态数据成员
};

int fun::Sum = 0;//定义并初始化静态数据成员

fun::fun(int a, int b, int c) {
Sum += a + b + c;
}

void fun::GetSum() {
std::cout << "Sum = " << Sum << std::endl;
}

int main() {
fun M(1, 2, 3);
M.GetSum();
fun N(4, 5, 6);
N.GetSum();
M.GetSum();
return 0;

}

可以看出, 静态数据成员有以下特点:

  • 对于非静态数据成员, 每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个, 静态数据成员在程序中也只有一份拷贝, 由该类型的所有对象共享访问。也就是说, 静态数据成员是该类的所有对象所共有的。对该类的多个对象来说, 静态数据成员只分配一次内存, 供所有对象共用。所以, 静态数据成员的值对每个对象都是一样的, 它的值可以更新;
  • 静态数据成员存储在全局数据区。静态数据成员定义时要分配空间, 所以不能在类声明中定义。在上述代码中, 语句int Myclass::Sum=0;是定义静态数据成员;
  • 静态数据成员和普通数据成员一样遵从public , protected , private访问规则;
  • 因为静态数据成员在全局数据区分配内存, 属于本类的所有对象共享, 所以, 它不属于特定的类对象, 在没有产生类对象时其作用域就可见, 即在没有产生类的实例时, 我们就可以操作它;
  • 静态数据成员初始化与一般数据成员初始化不同。静态数据成员初始化的格式为:
    <数据类型><类名>::<静态数据成员名>=<值>
  • 类的静态数据成员有两种访问形式:
    <类对象名>.<静态数据成员名> 或 <类类型名>::<静态数据成员名>
    如果静态数据成员的访问权限允许的话(即public的成员), 可在程序中, 按上述格式来引用静态数据成员 ;
  • 静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类, 每个实例的利息都是相同的。所以, 应该把利息设为存款类的静态数据成员。这有两个好处, 第一, 不管定义多少个存款类对象, 利息数据成员都共享分配在全局数据区的内存, 所以节省存储空间。第二, 一旦利息需要改变时, 只要改变一次, 则所有存款类对象的利息全改变过来了;
  • 同全局变量相比, 使用静态数据成员有两个优势:
    • 静态数据成员没有进入程序的全局名字空间, 因此不存在与程序中其它全局名字冲突的可能性;
    • 可以实现信息隐藏。静态数据成员可以是private成员, 而全局变量不能;

静态成员函数

与静态数据成员一样, 我们也可以创建一个静态成员函数, 它为类的全部服务而不是为某一个类的具体对象服务。静态成员函数与静态数据成员一样, 都是类的内部实现, 属于类定义的一部分。普通的成员函数一般都隐含了一个this指针, this指针指向类的对象本身, 因为普通成员函数总是具体的属于某个类的具体对象的。通常情况下, this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比, 静态成员函数由于不是与任何的对象相联系, 因此它不具有this指针。从这个意义上讲, 它无法访问属于类对象的非静态数据成员, 也无法访问非静态成员函数, 它只能调用其余的静态成员函数。下面举个静态成员函数的例子。

#include <iostream>

class fun {
public:
fun(int a, int b, int c);

static void GetSum(); //声明静态成员函数
private:
int a;
static int Sum;//声明静态数据成员
};

int fun::Sum = 0;//定义并初始化静态数据成员

fun::fun(int a, int b, int c) {
this->a = a;
Sum += a + b + c; //非静态成员函数可以访问静态数据成员
}

void fun::GetSum() //静态成员函数的实现
{
// std::out << a << std::endl; //错误代码, a是非静态数据成员, 编译时报错:Invalid use of member 'a' in static member function
std::cout << "Sum = " << Sum << std::endl;
}

int main() {
fun M(1, 2, 3);
M.GetSum();
fun N(4, 5, 6);
N.GetSum();
fun::GetSum(); // 注意与之前的调用区别
}

运行效果

"/mnt/d/Computer/C++ learns/cmake-build-release/Cpp_learns"
Sum = 6
Sum = 21
Sum = 21

Process finished with exit code 0

关于静态成员函数, 可以总结为以下几点:

  • 出现在类体外的函数定义不能指定关键字static;

  • 静态成员之间可以相互访问, 包括静态成员函数访问静态数据成员和访问静态成员函数;

  • 非静态成员函数可以任意地访问静态成员函数和静态数据成员;

  • 静态成员函数不能访问非静态成员函数和非静态数据成员;

  • 由于没有this指针的额外开销, 因此静态成员函数与类的全局函数相比速度上会有少许的增长;

  • 调用静态成员函数, 可以用成员访问操作符(.)和(->)为一个类的对象或指向类对象的指针调用静态成员函数, 也可以直接使用如下格式:

    <类名>::<静态成员函数名>(<参数表>)

    调用类的静态成员函数