C++基础整理(1)
注:整理一些突然学到的C++知识,随时mark一下
例如:忘记的关键字用法,新关键字,新数据结构
const关键字和static关键字用法整理
提示:本文为 C++ 中常用的 const 和 static 的用法和举例
一、const
在C++中,const关键字用于声明一个常量,即一个值在程序运行期间不能被修改的对象。const可以用于变量、指针、函数参数和返回值,以及成员函数和类。
1、常量变量
常量变量在声明时必须初始化,并且其值在程序的生命周期内保持不变。
代码如下(示例):
const int a = 10; // a是一个常量整数,其值为10
2、常量指针与指针常量
常量指针:指向常量的指针,即指针所指向的内容不能被修改。
代码如下(示例):
const int *p = &a; // p是一个指向整数的常量指针,你不能通过p来改变a的值
// *p = 20; // 这会导致编译错误
指针常量:指针本身是常量,即指针本身不能被重新赋值指向其他地址。
代码如下(示例):
int b = 20;
int *const q = &b; // q是一个指针常量,指向整数b
// q = &a; // 这会导致编译错误,因为q是常量
3、常量引用
常量引用是一个引用,它引用的对象不能被修改。
代码如下(示例):
int a = 1;
const int &ref = a; // ref是a的常量引用,你不能通过ref来改变a的值
// ref = 30; // 这会导致编译错误
4、常量的成员函数
常量成员函数是那些不会(也不应该)修改对象状态的成员函数。它们在函数声明后使用const关键字标记。
代码如下(示例):
class MyClass {
public:
int getValue() const { // 常量成员函数
return value;
}
private:
int value;
};
注意:如果是常量对象,那么它只能调用常量成员函数。
代码如下(示例):
const MyClass obj;
int val = obj.getValue(); // 正确,getValue()是常量成员函数
// obj.nonConstFunction(); // 错误,如果MyClass有一个非const成员函数,则不能通过const对象调用它,假设MyClass有一个非const的方法nonConstFunction
5、常量函数参数和返回值
函数参数或者返回值也可以被声明为const,以表示它们不会在函数内部被修改。
代码如下(示例):
void func(const int param) {
// param = 10; // 这会导致编译错误,因为param是常量参数
}
const int getConstant() {
int localVar = 10;
return localVar; // 返回值是const的,但localVar本身不是const
}
6、常量表达式与constexpr
在C++11及更高版本中,constexpr用于声明一个常量表达式,它在编译时就能被求值。
代码如下(示例):
constexpr int a = 5;
constexpr int b = a * 2; // b的值在编译时就能确定,为10
使用const和constexpr可以帮助程序员写出更安全、更可预测的代码,因为它们限制了变量的修改,并允许编译器进行更多的优化。然而,过度使用它们也可能导致代码变得过于僵硬和难以维护。因此,在使用这些关键字时,应该权衡其优缺点。
7、类的常成员变量
在C++中,类成员变量确实可以使用const关键字进行声明,从而创建常量成员变量。常量成员变量在类的所有对象中保持相同的值,并且其值在对象的生命周期内不能被修改。这提供了一种机制来确保某些关键数据成员在类的实例中保持不变。
常量成员变量必须在构造函数的初始化列表中初始化,因为常量成员变量没有默认的构造函数调用,也不能在构造函数体内部赋值。这是因为常量成员变量在构造函数的主体执行之前就已经被初始化了。
下面是一个简单的示例,展示了如何在C++类中使用const成员变量:
代码如下(示例):
class MyClass {
public:
MyClass(int value) : constantValue(value) {} // 初始化列表用于初始化const成员
void printValue() const {
std::cout << "Constant value: " << constantValue << std::endl;
}
private:
const int constantValue; // const成员变量
};
int main() {
MyClass obj(42); // 使用初始化列表中的值初始化const成员变量
obj.printValue(); // 输出: Constant value: 42
// obj.constantValue = 10; // 错误:不能修改const成员变量的值
return 0;
}
在上面的例子中,MyClass有一个私有的常量成员变量constantValue,它在构造函数的初始化列表中通过参数value进行初始化。一旦对象被创建,constantValue的值就不能再被修改。尝试修改它会导致编译错误。
请注意,常量成员变量可以是静态的或非静态的。[静态常量成员变量]需要在类定义外部进行定义和初始化,而[非静态常量成员变量]则通过构造函数的初始化列表进行初始化。
静态常量成员变量示例:
class MyClass {
public:
static const int staticConstantValue = 42; // 静态常量成员变量
// ... 其他成员 ...
};
// 静态常量成员变量在类外部不需要再次定义,但如果是整型或枚举类型,并且未在声明时初始化,则需要这样做:
const int MyClass::staticConstantValue; // 如果在类声明中没有初始化,则在这里初始化
静态常量成员变量在整个程序中只有一个副本,并且可以通过类名直接访问,而不需要创建类的实例。如果静态常量成员变量是整型或枚举类型,并且没有在声明时初始化,那么它必须在类定义外部进行定义(尽管不需要赋值,因为已经在声明时赋值了)。如果静态常量成员变量是复杂类型(如类或结构体),则必须在类外部定义并初始化。
二、static 关键字 in C++
1、局部静态变量:
当static修饰函数内部的变量时,它表示该变量的生命周期是整个程序的执行期间,而不是仅仅在包含它的函数被调用时。它只会被初始化一次,即在程序第一次进入定义该变量的函数时。这意味着每次调用函数结束后,静态局部变量的值都会被保留下来,而不是像自动变量那样被重新初始化。
代码如下(示例):
void func() {
static int count = 0; // 局部静态变量,只初始化一次
count++;
std::cout << count << std::endl;
}
int main() {
func(); // 输出: 1 ,第一次func调用完成后里面的变量count并不释放
func(); // 输出: 2
// ... 每次调用func(),count都会递增
return 0;
}
2、类的静态成员:
当static用于类的【成员变量】或【成员函数】时,它表示该成员与类本身相关联,而不是与类的任何特定对象相关联。所有类的对象共享同一个静态成员。静态成员变量必须在类定义外部进行初始化。静态成员函数只能访问静态成员变量或其他静态成员函数,它们不能访问类的非静态成员。
代码如下(示例):
class MyClass {
public:
static void staticFunc() { // 类静态成员函数
std::cout << staticVar << std::endl;
}
static int staticVar; // 类静态成员变量
};
int MyClass::staticVar = 0; // 静态成员变量在类外部初始化
int main() {
MyClass::staticVar = 42;
MyClass::staticFunc(); // 输出: 42
return 0;
}
3、静态类内部对象:
在类内部,static可以用来定义其他类的静态对象。这些对象在程序启动时创建,在程序结束时销毁,并且只创建一次。它们与类的任何特定实例无关。
代码如下(示例):
class Outer {
public:
static Inner staticInner; // 静态类内部对象
};
class Inner {
// ...
};
Inner Outer::staticInner; // 在类外部定义静态对象
4、全局/命名空间作用域中的 静态变量:
在全局或命名空间作用域中,static用于限制变量的链接性。这使得该变量只对其定义的文件可见,从而提供了文件级别的封装。在其他文件中,无法直接访问这个静态全局变量。
代码如下(示例):
// file1.cpp
static int fileScopeStatic = 0; // 只在file1.cpp中可见
// file2.cpp
extern int fileScopeStatic; // 错误:file2.cpp无法看到file1.cpp中的静态全局变量
5、关于mutable
在C++中,mutable关键字用于类的非静态数据成员,它允许在常量成员函数(即被声明为const的成员函数)中修改该成员。这通常用于那些逻辑上应为常量,但在某些上下文中确实需要被修改的类成员。使用mutable的一个典型场景是当你有一个类,该类包含一个缓存值或统计信息,这些值可以在不改变对象逻辑状态的情况下被修改。在这种情况下,即使对象本身是常量(即指向它的指针或引用是const),你也可能希望更新这些缓存值或统计信息。
下面是一个简单的示例,展示了mutable的用法:
class MyClass {
private:
mutable int mutableMember; // 这是一个可变的成员变量
int regularMember; // 这是一个普通的成员变量
public:
MyClass() : mutableMember(0), regularMember(0) {} //初始化
void setRegularMember(int value) {
regularMember = value;
}
int getMutableMember() const {
// 在const成员函数内部,我们可以修改mutable成员
++mutableMember; // 这是合法的,因为mutableMember是可变的
return mutableMember;
}
int getRegularMember() const {
// 尝试修改regularMember会导致编译错误,因为它不是mutable的
// regularMember++; // 这会导致编译错误
return regularMember;
}
};
int main() {
const MyClass obj; // 创建一个const对象
std::cout << "Mutable member: " << obj.getMutableMember() << std::endl; // 输出: 1
std::cout << "Mutable member again: " << obj.getMutableMember() << std::endl; // 输出: 2
// obj.setRegularMember(5); // 这会导致编译错误,因为obj是const的
return 0;
}
在这个例子中,mutableMember是一个可变的成员变量,它可以在const成员函数getMutableMember中被修改。当我们多次调用getMutableMember时,mutableMember的值会增加。然而,尝试在const成员函数内部修改非mutable成员regularMember会导致编译错误。需要注意的是,过度使用mutable可能会使代码难以理解和维护,因为它允许在逻辑上应该是常量的上下文中修改成员变量。因此,在使用mutable时应该谨慎,并确保它的使用是合理的。
6、静态断言:
C++11引入了static_assert,它允许在编译时进行条件检查。如果条件不满足,编译器将发出错误消息。
代码如下(示例):
static_assert(sizeof(int) == 4, "int must be 4 bytes!");