自学内容网 自学内容网

【C++】类中的特殊成员——静态成员,友元成员,常量成员

下图为笔者根据自己的理解做的图,仅供参考~
在这里插入图片描述

一.静态成员static

*类外

静态变量或函数意味着,当需要将这些变量或函数与实际定义的符号链接时,链接器不会在这个翻译单元的作用域之外寻找那个符号定义,即只会在这个翻译单元内部链接(文件内可用)

代码实例

//Main.cpp
#include<iostream>
int s_Variable = 10;
int main() {
std::cout << s_Variable << std::endl;
}

//Static.cpp
static int s_Variable = 5;//类外声明静态整型变量

//输出:10

当删掉Static.cpp中的static int s_Variable = 5;中的static后,报错了

我们可以将Main.cpp中的int s_Variable = 10;改为extern int s_Variable;

->没有问题,输出5

C++中extern关键字的用法

在C++中,extern关键字用于声明变量或函数是在其他文件中定义的。这是一种链接不同编译单元中的全局变量和函数的方法。使用 extern可以在一个文件中定义全局变量或函数,并在其他文件中访问它们,从而实现跨文件共享

类内

1.1静态数据成员

指的是在C++类中声明成员时,可以加上static关键字,这样声明的成员叫静态成员。静态成员分为静态数据成员和静态函数成员两种。

  1. 定义
class node{
    public:
    static int id;//静态数据成员定义
}
int node::id=10;//静态数据成员类外初始化
  1. 特点
  • 类中的静态数据成员,所有对象都共享该数据,只有一份内存在内存中
  • 类中的静态数据成员,必须要在类外初始化,因为它不属于对象,而是属于类。对象不管是否存在,这个静态数据成员都是存在的,而且静态数据成员的生命周期是程序开始就存在(主函数运行之前),直到程序结束才会被释放
  • 类中的静态数据成员,可以在类中被重新赋值,可以被普通函数访问,如果该成员是公有属性,那么还可以在类外被对象自己访问(没什么意义),或者通过类名访问

1.2静态函数成员

  1. 定义
class node{
    public:
    static void fun(){}//在类中定义
    static void fun1();//在类中声明
}
void node::fun1(){}//在类外定义
  1. 特点
  • 类中的静态函数成员,这个函数同样也不属于对象,而是属于类的,所以在这个函数中不能操作类中的普通数据成员和普通成员函数。因为这些普通成员是必须要有对象的时候才会被建立,而静态函数不用对象也能调用。也就是说我们不用实例化对象就能使用静态变量和方法(下面的例子会讲到)
  • 访问和静态数据成员一致
  • 可以在这个静态函数中使用局部变量、形参、静态数据成员

【代码示例】

原始代码->

#include<iostream>
class Entity {
public:
int x, y;
void print() {
std::cout << x << "," << y << std::endl;
}
};

int main() {
Entity e;
e.x = 2;
e.y = 3;
Entity e1;
e1.x = 5;
e1.y = 8;
e.print();//2,3
e1.print();//5,8
}
//输出:2,3
//5,8

将x和y变为静态的:static int x,y;并在类外初始化该静态成员

#include<iostream>
class Entity {
public:
static int x, y;
void print() {
std::cout << x << "," << y << std::endl;
}
};
int Entity::x;
int Entity::y;

int main() {
Entity e;
e.x = 2;
e.y = 3;
    e.print();
Entity e1;
e1.x = 5;
e1.y = 8;  //被刷新了,相当于只有一个x,y值
e.print();
e1.print();
}
//输出:2,3
//5,8
//5,8

特点理解①——不用实例化对象就能使用静态变量和方法

e1.x = 5;像这样引用没什么意义

可以像这样引用:Entity::x = 5;

同样地,将print函数改为静态的

e.print();可以写成Entity::print();

于是,上面的代码改为->

#include<iostream>
class Entity {
public:
static int x, y;
static void print() {
std::cout << x << "," << y << std::endl;
}
};
int Entity::x;
int Entity::y;

int main() {
Entity::x = 6;
Entity::y = 7;
Entity::print();
}

特点理解②——不能用静态的方法访问非静态成员

#include<iostream>
struct Entity {
int x, y;
static void print() {
std::cout << x << "," << y << std::endl;
}
};

int main() {
Entity e;
e.x = 6;
e.y = 7;
Entity::print();
}
//报错:对非静态成员的“Entity::x”的非法引用

*不同属性下的静态成员

在面向对象编程中,公有属性下的静态成员私有属性下的静态成员主要有以下区别:

以下是用C++展示公有属性下的静态成员和私有属性下的静态成员区别的代码示例:

#include <iostream>
using namespace std;

class MyClass {
public:
    // 公有静态成员变量
    static int public_static_variable; 
    // 公有静态成员函数,用于访问私有静态成员变量(一种间接访问方式示例)
static void access_private() {
    cout << private_static_variable << endl;
}
private:
    // 私有静态成员变量
    static int private_static_variable; 
};

// 类外初始化公有静态成员变量
int MyClass::public_static_variable = 10;
// 类外初始化私有静态成员变量
int MyClass::private_static_variable = 20;

int main() {
    // 直接访问公有静态成员变量
    cout << MyClass::public_static_variable << endl; 
    // 通过公有静态成员函数间接访问私有静态成员变量
MyClass::access_private(); 
return 0;
}
//输出:10
//20

在上述代码中:

①访问权限方面

  • 公有静态成员变量 public_static_variable:在 main 函数等类外部的地方,可以通过 类名::成员名 (如 MyClass::public_static_variable )的方式直接访问,体现了公有能被外部自由访问的特点。
  • 私有静态成员变量 private_static_variable :它不能在类外部直接通过 类名::成员名 的方式访问,只能在类内部的成员函数中访问(像示例里的 access_private 函数),保证了外部代码无法直接触及。

②作用和用途方面

  • 公有静态成员变量和函数:比如 public_static_variable可以作为类对外提供的共享数据,外部代码能方便获取或修改(如果需要), access_private函数这种公有静态成员函数可以作为类对外提供的操作接口,合理控制对内部私有静态成员的访问等情况。
  • 私有静态成员变量:可以用来保存类内部一些不想被外部随意更改,只供内部逻辑使用的数据,例如在更复杂的类设计中,可能用于记录类内部状态等关键的、不希望外部干扰的信息。

1.3局部静态(Local Static)

静态局部变量允许我们声明一个变量,它的生命周期基本上相当于整个程序的生命周期,但是它的作用域范围是被限制的。你可以在任何作用域中声明,如果在函数中声明,那么它的作用域范围被限制在这个函数中。

示例:

#include<iostream>
void Function() {
static int i = 0;
i++;
std::cout << i << std::endl;
}

int main() {
Function();
Function();
Function();
Function();
Function();
}
//输出:1
//2
//3
//4
//5

这意味着当我第一次调用函数时,这个变量将被初始化为0,初始化值0会贯穿整个程序的生命周期,每一次运行函数都会刷新值,即所有对函数的后续调用实际上不会创建一个全新的变量

而下面这种情况就不会这样->

#include<iostream>
void Function() {
int i = 0;
i++;
std::cout << i << std::endl;
}

int main() {
Function();
Function();
Function();
Function();
Function();
}
//输出:1
//1
//1
//1
//1

改变作用域范围也会达到同样的效果->

#include<iostream>
int i = 0;
void Function() {
i++;
std::cout << i << std::endl;
}

int main() {
Function();
Function();
Function();
Function();
Function();
}
/*输出:1
2
3
4
5
*/

但这种方法的问题是我可以在任意地方访问 i

Function();
i=10;
Function();
Function();
Function();
Function();
/*输出:1
11
12
13
14
*/

总结

static的五种用法:

  1. 全局变量前加static修饰为文件作用域全局变量,内存在全局数据区
  2. 块作用域前加static修饰为块作用域变量,但内存在全局数据区
  3. 普通函数前加static修饰为文件作用域函数
  4. 类中数据成员前加static修饰为类中所有对象共享数据
  5. 类中函数成员前加static修饰为类中所有对象共享成员

二.常量成员

常量成员,指的是在C++类中声明对象成员时可以加上const关键字

这样声明的成员叫常量成员,常量成员分为常量数据成员和常量函数成员

2.1常量数据成员

定义

class node{
    const int id;
}

初始化

可以在定义的时候直接在后面赋值:const int id=10;//不建议使用

或者通过初始化列表的方式初始化

初始化列表的使用

初始化列表要写在构造函数的后面,如下:

class node {
const int id;
public:
node():id(10)
{}//把10赋值给id
};

当然,普通的数据成员也可以这样初始化

class node{
    const int id;
    int age;
public:
    node ():id(10),age(20)
    {}//把10赋值给id,20赋值给age
};//如果要是给类中的多个数据初始化,中间用逗号隔开

初始化列表的特性

  1. 初始化列表也是实现类中成员数据初始化的一种方式
  2. 一些特殊情况下,数据成员的初始化只能用初始化列表的方式给数据成员赋值,而不能在构造函数中直接赋值
  3. 初始化列表必须写在构造函数的定义体后面
  4. 构造函数能对数据的初始化工作,初始化列表也可以,但是初始化列表能做的,构造函数不一定能
  5. 初始化列表的初始化顺序是按照他们在类中出现的顺序来初始化的,而不是在初始化列表中的顺序来初始化的

2.2常量函数成员

定义

class node {
int id = 10;
public:
void fun() const {
id = 10;//报错,这是反例
}//下面是正确用法
void fun1() const;//类内声明
    //const void fun1(){} 这样写的话,const是修饰返回值类型的
};
void node::fun1() const{}//类外实现
//在这个函数中,不能修改类中的数据成员(静态数据可以)

特点

类中的常量函数成员,在这个函数中不能对自身变量进行修改。通过语法限定,只要是this指针所指向的所有数据都不可以被修改(静态数据可以改变)。这样,类自身在调用该函数时,无法修改其成员数据,从而实现对数据修改权限的约束。

2.3常量对象

  1. 在对象实例化时在类名前面加上const修饰,该对象为常量对象,满足常量的操作,定义时必须初始化
  2. 该对象里面的所有数据都不能被修改,因此对象里面的普通成员函数不允许被调用,只允许调用常量函数成员

三.友元成员

类的特性之一就是封装,而友元就是C++为用户提供打破这种封装的手段,友元分为友元函数和友元对象——

3.1友元函数

  • 友元函数,他只是一个函数,友元函数不是类的成员,通过类对象是无法访问的,但是在这个函数中有权通过对象访问类中的所有成员
  • 友元函数,无论声明在类中的任何访问属性下都可以,不影响他的调用和访问

示例:

#include<iostream>
class node {
    int id = 10;
    friend void fun(node& n);
};
void fun(node& n) {
    std::cout << n.id << std::endl;
}
int main() {
    node n;
    fun(n);//输出10
}

3.2友元类

友元类是一个单独的类,只不过和友元函数一样,在类中声明了一个友元类,在这个友元类中同样也可以访问该类中的所有成员,在A类中声明B类为A类的友元类,那么在B类中就可以访问A类中的所有成员

示例:

#include<iostream>
class A {
    friend class B;
    int id = 10;
};
class B {
public:
    void fun(A& a) {
        std::cout << a.id << std::endl;
    }
};
int main() {
    A a;
    B b;
    b.fun(a);//输出10
}

友元的特性

  1. 单方向:B是A的朋友,B可以访问A的数据,A不可以访问B的数据
  2. 不传递:A是B的朋友,B是C的朋友,A和C没有朋友关系
  3. 不继承:A是B的朋友,B是C的父亲,A和C没有朋友关系

总结

在C++中,普通成员、静态成员、常量成员与友元成员之间有以下联系:

普通成员与静态成员

  • 共享与独立:普通成员每个类对象都有独立的一份,对象之间互不影响;静态成员被类的所有对象共享,不管创建多少对象,静态成员只有一份。例如,统计类实例化对象的数量,适合用静态成员变量。
  • 访问方式:普通成员通过对象访问,静态成员可以通过类名或对象访问。

普通成员与常量成员

  • 初始化与不变性:普通成员可被修改;常量成员在初始化后不能被修改,且必须在构造函数初始化列表中初始化,如定义一个表示圆半径的常量成员,初始化后就不能改变。
  • 函数成员的使用:常量成员函数保证不修改对象状态,普通成员函数可以修改。

普通成员与友元成员

  • 访问权限:普通成员遵循类的访问控制,私有成员只能在类内访问;友元函数或友元类可以访问类的私有和保护成员,打破了类的封装性。例如,在实现重载流插入和流提取运算符时,常将其声明为类的友元函数来访问类的私有成员进行输出和输入操作。

静态成员与常量成员

  • 共享的不变性:静态常量成员结合了两者特性,被类的所有对象共享且不可修改,像定义一个表示数学常量π的静态常量成员,供类的所有方法使用。

静态成员与友元成员

  • 友元对静态的访问:友元函数或友元类可以访问类的静态成员,访问方式与访问普通静态成员相同。

常量成员与友元成员

  • 友元对常量的访问:友元可以访问常量成员,不过要遵守常量成员的不可修改规则。

原文地址:https://blog.csdn.net/2301_79279099/article/details/144410815

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!