【C++基础】static、const在类中的应用

目录

static

一、修饰的变量或函数类型

1. 修饰全局变量

2. 修饰局部变量

1. 通过函数访问

2. 通过类的静态成员

3. 修饰函数

4. 修饰类中的成员

二、在类中的应用场景

1. 共享数据(跨对象共享状态)

2. 单例模式

3. 工具类或辅助函数

4. 类级别的常量

5. 计数器或标识符生成器

6. 缓存共享数据

const

一、修饰的变量和函数类型

1. const修饰的变量

2. const修饰的成员变量

3. const修饰的成员函数

3. const修饰的返回值

4. const修饰的常量表达式

5. const修饰的友元函数

友元函数中的const参数:

二、相关内容补充

友元函数的介绍

友元函数的特点和用法

友元类

const和constexpr的区别

1. 定义和目的

2. 编译时与运行时

3. 函数使用

4. 使用场景

5. 构造函数


如需咨询请添加个人微信:a15135158368

欢迎叨扰,多多交流

static

一、修饰的变量或函数类型

1. 修饰全局变量

static 修饰全局变量,限制作用为当前源文件

也就是说,其他源文件不能访问这个变量。这通常用于实现文件内部的数据封装。

// file1.cpp
static int globalVar = 42; // 只能在 file1.cpp 中访问
​
void someFunction() {
    // 使用 globalVar
}
​
// file2.cpp
extern int globalVar; // 不能访问 file1.cpp 中的 globalVar
2. 修饰局部变量

static 修饰的局部变量的生命周期会延长到整个程序的运行时间.

局部 static 变量在函数调用结束后不会被销毁,而是保留其值,直到下一次函数调用。

(非static修饰的局部变量在其函数运行结束之后被销毁)

void myFunction() {
    static int counter = 0; // 只初始化一次
    counter++;
    std::cout << "Counter: " << counter << std::endl;
}
​
int main() {
    myFunction(); // 输出:Counter: 1
    myFunction(); // 输出:Counter: 2
    myFunction(); // 输出:Counter: 3
}

函数内部定义的 static 局部变量的作用范围是限于该函数内的

这意味着你不能直接从函数外部访问或修改这些 static 局部变量。

然而,有几种方法可以间接地访问或获取这些 static 局部变量的值:

1. 通过函数访问

你可以通过一个公共接口函数来访问或操作 static 局部变量。例如,你可以定义一个函数返回 static 局部变量的值,或者一个函数用于修改这个值。

#include <iostream>
​
int getCounter() {
    static int counter = 0; // static 局部变量
    return counter;
}
​
void incrementCounter() {
    static int counter = 0; // static 局部变量
    counter++;
}
​
int main() {
    incrementCounter();
    std::cout << "Counter: " << getCounter() << std::endl; // 输出:Counter: 1
​
    incrementCounter();
    std::cout << "Counter: " << getCounter() << std::endl; // 输出:Counter: 2
​
    return 0;
}

在这个例子中,getCounterincrementCounter 函数用于访问和修改 static 局部变量 counter 的值。

2. 通过类的静态成员

如果你需要从函数外部获取类似功能的变量,你可以考虑将这个 static 变量放在类的静态成员中。这样,你可以通过类的静态成员函数访问或修改它。

#include <iostream>
​
class Counter {
public:
    static int getCounter() {
        return counter;
    }
​
    static void incrementCounter() {
        counter++;
    }
​
private:
    static int counter; // 静态成员变量
};
​
// 静态成员变量的定义
int Counter::counter = 0;
​
int main() {
    Counter::incrementCounter();
    std::cout << "Counter: " << Counter::getCounter() << std::endl; // 输出:Counter: 1
​
    Counter::incrementCounter();
    std::cout << "Counter: " << Counter::getCounter() << std::endl; // 输出:Counter: 2
​
    return 0;
}

在这个例子中,counter 是一个静态成员变量,getCounterincrementCounter 是静态成员函数,用于访问和修改这个变量。

注意:访问静态成员变量并不是必须为静态成员函数,也可以通过非静态成员函数访问。

但是,静态成员函数不能访问非静态成员变量。

3. 修饰函数

static 修饰一个函数时,这个函数的作用范围被限制在定义它的源文件中。

这在实现文件内部的帮助函数时很有用,防止函数名冲突。

// file1.cpp
static void helperFunction() {
    // 内部使用的辅助函数
}
​
// file2.cpp
void anotherFunction() {
    // 无法调用 file1.cpp 中的 helperFunction
}

4. 修饰类中的成员

在类中,static 修饰符用来定义类的静态成员。静态成员属于整个类,而不是某个对象。

它们在所有对象间共享,且可以通过类名直接访问同一个变量的地址空间

class MyClass {
public:
    static int staticVar; // 静态成员变量
    static void staticMethod() { // 静态成员函数
        std::cout << "Static method called!" << std::endl;
    }
};
​
// 静态成员变量的定义
int MyClass::staticVar = 0;
​
int main() {
    MyClass::staticVar = 10; // 通过类名访问
    MyClass::staticMethod(); // 调用静态方法
}

二、在类中的应用场景

主要用于管理类级别的成员变量和函数。以下是一些常见的应用场景:

1. 共享数据(跨对象共享状态)

在某些情况下,多个对象需要共享相同的数据。

通过将数据声明为静态成员变量,所有对象都可以访问和修改这个共享数据。例如,计数类实例的数量、共享配置参数等。

class Counter {
public:
    Counter() {
        ++count; // 增加实例计数
    }
​
    static int getCount() {
        return count;
    }
​
private:
    static int count; // 静态成员变量,用于计数
};
​
int Counter::count = 0;
​
int main() {
    Counter c1;
    Counter c2;
​
    std::cout << "Number of Counter objects: " << Counter::getCount() << std::endl;
    return 0;
}

在这个例子中,无论创建多少个 Counter 对象,静态变量 count 都能跟踪当前对象的数量。

2. 单例模式

单例模式是一种设计模式,确保一个类只有一个实例,并提供一个全局访问点。

静态成员变量和静态成员函数可以实现这一模式。

class Singleton {
public:
    // 静态成员函数,返回Singleton类的唯一实例
    static Singleton& getInstance() {
        // 静态局部变量,保证在函数首次调用时初始化,并且在程序生命周期内只初始化一次
        static Singleton instance;
        return instance; // 返回该唯一实例的引用
    }
​
private:
    // 私有构造函数,防止类外部直接创建对象实例
    Singleton() {}
​
    // 删除拷贝构造函数,防止通过拷贝创建多个实例
    Singleton(const Singleton&) = delete;
​
    // 删除赋值操作符,防止通过赋值操作创建多个实例
    Singleton& operator=(const Singleton&) = delete;
};
​
int main() {
    // 获取Singleton类的唯一实例
    Singleton& s1 = Singleton::getInstance();
​
    // 再次获取Singleton类的唯一实例
    Singleton& s2 = Singleton::getInstance();
​
    // s1 和 s2 是同一个实例,验证了单例模式的正确性
}

3. 工具类或辅助函数

有些函数不需要访问类的成员变量,而是执行与类相关的某些操作,这些函数可以被声明为静态成员函数。例如,数学计算、字符串操作等函数。

class MathUtils {
public:
    static int add(int a, int b) {
        return a + b;
    }
​
    static int multiply(int a, int b) {
        return a * b;
    }
};
​
int main() {
    int sum = MathUtils::add(3, 4);
    int product = MathUtils::multiply(3, 4);
​
    std::cout << "Sum: " << sum << ", Product: " << product << std::endl;
    return 0;
}

4. 类级别的常量

在某些情况下,类级别的常量需要在多个对象中使用,使用静态成员变量可以实现这一目的。

class Circle {
public:
    //constexpr 关键字修饰的变量:将该变量在编译时就可以计算出来,并且结果是一个常量
    //constexpr 函数:必须是一个单一的返回表达式,或者能够在编译时求值的逻辑。
    static constexpr double PI = 3.14159;
​
    Circle(double radius) : radius(radius) {}
​
    double getArea() const {
        return PI * radius * radius;
    }
​
private:
    double radius;
};
​
//计算圆形的面积
int main() {
    //设置私有变量的值
    Circle c1(10.0);
    std::cout << "Area: " << c1.getArea() << std::endl;
    return 0;
}

在这个例子中,PI 是类级别的常量,所有 Circle 对象都可以使用它。

5. 计数器或标识符生成器

静态成员变量可以用于实现全局唯一的计数器或标识符生成器,比如为每个对象分配一个唯一的 ID。

class IDGenerator {
public:
    IDGenerator() : id(nextID++) {}
​
    int getID() const 
    {
        return id;
    }
​
private:
    static int nextID; // 静态成员变量,用于生成唯一 ID
    int id;
};
​
int IDGenerator::nextID = 1;
​
int main() {
    IDGenerator obj1;
    IDGenerator obj2;
​
    std::cout << "ID of obj1: " << obj1.getID() << std::endl;
    std::cout << "ID of obj2: " << obj2.getID() << std::endl;
​
    return 0;
}

6. 缓存共享数据

静态成员变量可以用来缓存计算结果或共享数据,避免重复计算或加载。例如,在大型项目中,可能需要缓存配置数据或计算结果。

class Config {
public:
    static void loadConfig() {
        if (!configLoaded) {
            // 假设这里加载配置数据
            configLoaded = true;
            std::cout << "Configuration loaded." << std::endl;
        }
    }
​
private:
    static bool configLoaded; // 标志配置是否已经加载
};
​
bool Config::configLoaded = false;
​
int main() {
    Config::loadConfig(); // 第一次加载
    Config::loadConfig(); // 不会再次加载
​
    return 0;
}

const

一、修饰的变量和函数类型

const关键字在C++中用于表示不可修改的值或函数行为。

它可以修饰变量、成员变量、成员函数以及友元函数。下面详细解释它在这些不同场景中的作用:

1. const修饰的变量

  • 全局或局部变量: 使用const修饰的变量在其声明之后不能被修改。例如:

    const int a = 10;
    // a = 20; // 错误,a 是 const,不能修改

  • 指针变量: const可以修饰指针本身或者指针所指向的值,具体的用法如下:

    const int *ptr1 = &a;  // 指向 const int 的指针,不能通过 ptr1 修改 a
    int *const ptr2 = &a;  // const 指针,ptr2 指向的地址不能改变
    const int *const ptr3 = &a;  // 指向 const int 的 const 指针,ptr3 不能修改指向的地址或值

2. const修饰的成员变量

  • 类中的const成员变量: 使用const修饰的成员变量必须在初始化列表中初始化,且在对象的生命周期内不能被修改。例如:

    class MyClass {
    private:
        const int x;
    ​
    public:
        MyClass(int val) : x(val) {}  // x 必须在初始化列表中初始化
    };

3. const修饰的成员函数

  • const成员函数: const成员函数表示该函数不会修改类的成员变量(除了使用mutable修饰的成员变量)。

    这种函数只能调用其他const成员函数,不能调用非const成员函数。例如:

    class MyClass {
    private:
        int x;
    ​
    public:
        int getValue() const {  // 这是一个 const 成员函数
            // x++;  // 错误,不能在 const 成员函数中修改成员变量
            return x;
        }
    };

3. const修饰的返回值

  • 返回const对象: 如果一个成员函数返回类的对象或引用,并且希望确保返回的对象不会被修改,可以将返回类型声明为const

    class MyClass {
    private:
        int value;
    ​
    public:
        const int& getValue() const {
            return value;
        }
    };

    应用场景:

    • 当你希望调用者无法修改函数返回的对象时,可以使用const返回类型。这可以有效防止数据被意外篡改。

4. const修饰的常量表达式

  • constexprconst结合constexpr通常与const结合使用,表示在编译时就能确定的常量。这可以用于类的静态成员变量或函数中,提供常量表达式计算的能力。

    class MyClass {
    public:
        static constexpr int getConstant() {
            return 42;
        }
    };

    应用场景:

    • 当你需要在编译时确定常量值,并且希望该值在整个程序中保持不变时,constexprconst的结合是理想的选择。

5. const修饰的友元函数

友元函数中的const参数:

友元函数可以通过const参数来限制对对象的修改。

虽然友元函数本身不属于类成员,但它可以访问类的私有成员。因此,通过将参数声明为const,可以确保友元函数不会修改传入的对象。例如:

class MyClass {
private:
    int x;
​
public:
    MyClass(int val) : x(val) {}
​
    friend void printValue(const MyClass& obj);  // 友元函数,参数是 const 引用
};
​
void printValue(const MyClass& obj) {
    std::cout << obj.x << std::endl;
    // obj.x = 20;  // 错误,obj 是 const,不能修改
}

二、相关内容补充

友元函数的介绍

它虽然不是某个类的成员函数,但却能够访问该类的私有和保护成员。

友元函数的主要用途是实现需要直接访问类内部数据的全局函数或其他类的成员函数。

友元函数的特点和用法
  1. 友元函数的定义: 友元函数是在类内部通过关键字friend声明的,但它本身并不是类的成员。

    友元函数可以是普通的全局函数、其他类的成员函数,甚至是友元类的成员函数。

    class MyClass {
    private:
        int x;
    ​
    public:
        MyClass(int val) : x(val) {}
    ​
        friend void printValue(const MyClass& obj); // 声明友元函数
    };
    ​
    // 友元函数定义
    void printValue(const MyClass& obj) {
        std::cout << "Value of x: " << obj.x << std::endl; // 可以访问私有成员 x
    }

  2. 友元函数的访问权限: 友元函数能够访问类的所有成员,包括私有成员和保护成员。

  3. 友元函数不是成员函数: 友元函数并不是该类的成员函数,因此它不具有this指针。

    友元函数不依赖于对象来调用,它是一个独立的函数。

  4. 友元函数的常见应用场景:

    • 重载运算符: 友元函数常用于运算符重载,特别是需要两个不同类型的操作数时。例如,重载<<运算符输出对象的内容。

    • 实现跨类操作: 友元函数可以访问两个类的私有成员,在实现两个类之间的紧密协作时非常有用。

  5. 友元函数的声明与定义: 友元函数的声明通常放在类的内部,表示这个函数是该类的友元;

    而函数的定义则可以在类的外部实现。注意,友元函数的定义不需要使用friend关键字。

    class MyClass {
    private:
        int x;
    ​
    public:
        MyClass(int val) : x(val) {}
    ​
        friend void printValue(const MyClass& obj);  // 声明友元函数
    };
    ​
    // 友元函数的定义
    void printValue(const MyClass& obj) {
        std::cout << "Value: " << obj.x << std::endl;
    }
    ​
    int main() {
        MyClass obj(42);
        printValue(obj);  // 直接调用友元函数
        return 0;
    }

  6. 友元函数与类的耦合度: 虽然友元函数为跨类操作提供了方便,但也增加了类与函数之间的耦合度,因此使用友元函数时要注意适度,避免过多的友元声明导致代码的复杂性增加。

友元类

除了友元函数,C++还允许声明整个类作为另一个类的友元。

这意味着友元类的所有成员函数都可以访问另一个类的私有和保护成员。例如:

class ClassB;  // 前向声明
​
class ClassA {
private:
    int a;
​
public:
    ClassA(int val) : a(val) {}
​
    friend class ClassB;  // 声明 ClassB 为友元类
};
​
class ClassB 
{
public:
    void showA(const ClassA& obj) 
    {
        std::cout << "ClassA's private member a: " << obj.a << std::endl; // 可以访问 ClassA 的私有成员
    }
};
​
int main() {
    ClassA objA(10);
    ClassB objB;
    objB.showA(objA);  // 通过 ClassB 的成员函数访问 ClassA 的私有成员
    return 0;
}

constconstexpr的区别

1. 定义和目的
  • const

    • const用于定义常量,表示其值在程序运行期间不可改变。可以用于变量、对象、函数参数、返回类型等。

    • const声明的变量在运行时才确定具体的值。

    const int x = 10;  // x 是一个常量,值为 10

  • constexpr

    • constexpr用于定义一个在编译期就能确定其值的常量。它可以用于常量表达式计算,确保在编译阶段就可以确定结果。

    • constexpr可以用于函数、变量、构造函数等,确保它们能够在编译时计算出值。

    constexpr int y = 10;  // y 是一个编译时常量,值为 10

2. 编译时与运行时
  • const

    • const变量的值可以在运行时确定,也可以在编译时确定。

    • const变量不一定是编译期常量。

  • constexpr

    • constexpr要求变量在编译时确定值。

    • constexpr变量必须是编译期常量。

3. 函数使用
  • const函数

    • const可以修饰类的成员函数,表示该函数不会修改类的成员变量。

    class MyClass {
    public:
        int getValue() const {
            return value;
        }
    private:
        int value;
    };

  • constexpr函数

    • constexpr函数要求在编译时即可执行,且返回值必须是编译期常量。它可以被用于常量表达式中。

    constexpr int square(int x) {
        return x * x;
    }
    constexpr int y = square(5);  // y 是一个编译时常量

4. 使用场景
  • const

    • 适用于需要在运行时或编译时确保值不变的场景。const常用于保护不应被修改的变量或参数。

  • constexpr

    • 适用于需要在编译时计算出结果的场景,例如用于常量表达式,数组大小,或模板参数等。constexpr能提高程序的运行效率,因为它能在编译时完成一些计算。

5. 构造函数
  • const

    • const不用于构造函数。

  • constexpr

    • constexpr构造函数用于定义一个类,该类的对象可以是编译时常量。

    struct Point {
        int x, y;
        constexpr Point(int a, int b) : x(a), y(b) {}
    };
    constexpr Point p(10, 20);  // p 是一个编译时常量对象

  • 18
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值