温岭建设网站,WordPress主题LensNews,wordpress下载流程,做网站还是租用服务器文章目录 一、C 存储类1. 类的定义2. 对象的创建3. 对象在内存中的布局4. 对象的存储位置 二、auto 存储类1. auto的基本用法2. auto与存储类的关系1) 自动存储类#xff08;最常见的#xff09;2) 静态存储类3) 动态存储类#xff08;通过new#xff09; 三、register 存储… 文章目录 一、C 存储类1. 类的定义2. 对象的创建3. 对象在内存中的布局4. 对象的存储位置 二、auto 存储类1. auto的基本用法2. auto与存储类的关系1) 自动存储类最常见的2) 静态存储类3) 动态存储类通过new 三、register 存储类1. register 存储类的特点2. 案例3. 注意事项 四、static 存储类1. 局部静态变量2. 全局静态变量3. 静态类成员变量4. 静态类成员函数 五、extern 存储类1. 声明全局变量2. 声明函数3. extern C4. 注意事项 六、mutable 存储类1. 基本用法2. 示例3. 注意事项 七、thread_local 存储类1. 基本用法2. 注意事项 八、相关链接 一、C 存储类
在C中类的存储方式主要涉及到类的定义、对象的创建以及这些对象在内存中的布局。C是一种静态类型、编译型语言支持面向对象的编程范式。下面详细解释这些概念
1. 类的定义
类是C中用户定义的类型UDT用于封装数据称为成员变量或属性和函数称为成员函数或方法。类的定义不会为对象分配任何内存它只是描述了对象的结构。
class MyClass {
public:int x;void func() {// 成员函数实现}
};2. 对象的创建
当你使用类来创建对象时才会在内存中为对象分配空间。这个空间用于存储对象的成员变量。成员函数则不直接存储在对象中而是存储在程序的代码段中对象通过其成员函数指针通常是通过隐式的this指针来访问这些函数。
MyClass obj; // 创建一个MyClass类型的对象obj3. 对象在内存中的布局
成员变量对象的成员变量包括从基类继承的成员变量按照它们在类中声明的顺序在内存中连续存储。成员变量的对齐和填充padding可能会根据编译器和平台的不同而有所不同以优化内存访问速度。成员函数成员函数不直接存储在对象中而是存储在程序的代码段Code Segment中。当对象调用成员函数时该函数通过隐式的this指针来访问对象的成员变量。静态成员静态成员变量在程序的全局数据段中分配一次内存所有对象共享同一份静态成员变量的拷贝。静态成员函数则不直接访问任何对象的成员变量除非通过对象或类名显式传递因此它们没有this指针。
4. 对象的存储位置
对象的存储位置取决于其生命周期和作用域
自动存储期在函数或代码块内声明的局部变量包括对象存储在栈Stack上其生命周期仅限于定义它们的代码块。静态存储期全局对象、静态局部变量以及静态成员变量存储在全局数据段Global Data Segment或静态数据段Static Data Segment中它们在程序的整个执行期间都存在。动态存储期通过new操作符动态分配的对象存储在堆Heap上其生命周期由new和delete操作符控制。
二、auto 存储类
在C中auto 关键字不是用来直接声明存储类的而是用来自动推导变量类型的。存储类也称为存储期或生命周期指的是变量在内存中保留的时间长度常见的存储类有自动automatic、静态static、寄存器register但现代C中很少使用且其优化行为通常由编译器自动处理、外部extern以及动态分配通过new操作符。
然而当我们在C中使用auto关键字时我们是在告诉编译器自动根据初始化表达式的类型来推导变量的类型。这并不意味着auto变量就有特定的存储类它的存储类取决于其声明的上下文。
1. auto的基本用法
下面是一个使用auto的简单案例它演示了如何自动推导变量的类型但并没有直接涉及到存储类的概念
#include iostream
#include vectorint main() {// 自动推导为int类型auto a 10;std::cout a is: a std::endl;// 自动推导为std::vectorint类型std::vectorint v {1, 2, 3, 4, 5};auto b v; // 注意这里b是v的一个拷贝而不是引用std::cout b has b.size() elements. std::endl;return 0;
}2. auto与存储类的关系
尽管auto本身不直接声明存储类但auto声明的变量可以根据其声明的上下文获得不同的存储类。以下是一些例子
1) 自动存储类最常见的
void func() {auto x 10; // x具有自动存储类因为它是在函数内部声明的局部变量// ...
} // x的作用域结束x的生命周期也结束2) 静态存储类
static auto y 20; // y具有静态存储类尽管它是用auto声明的
// y的生命周期贯穿整个程序执行期间但其作用域可能受限于声明它的块或文件void func() {// 可以在这里访问y
}注意在全局或命名空间作用域中使用static会改变链接性linkage但在函数内部使用时它主要影响生命周期和初始化时机只初始化一次。
3) 动态存储类通过new
虽然auto通常不直接与动态分配相关联但你可以使用auto来接收new表达式的结果
auto ptr new int(30); // ptr是指向int的指针具有自动存储类
// 但它指向的对象是通过new动态分配的具有动态存储类
// 记得用delete ptr;来释放内存在这个例子中ptr本身是一个具有自动存储类的局部变量但它指向的对象是通过new动态分配的因此该对象具有动态存储类。
总结auto用于自动推导变量类型而变量的存储类取决于其声明的上下文如是否在函数内部、是否使用了static关键字、是否通过new动态分配等。
三、register 存储类
在C中register 存储类是一个特殊的说明符用于向编译器建议将变量存储在CPU的寄存器中以便快速访问。然而需要注意的是register 关键字仅仅是一个建议并不保证变量一定会被存储在寄存器中。此外从C11开始register 关键字被废弃现代编译器通常会忽略它因为它们有自己的优化机制来决定变量的存储位置。
1. register 存储类的特点
建议性register 关键字告诉编译器程序员认为该变量应该被频繁访问因此建议将其存储在寄存器中。但编译器可以忽略这个建议。局部变量register 只能用于局部变量不能用于全局变量或静态变量。限制使用 register 的变量不能被取地址即不能应用一元 运算符因为它可能没有内存地址。尺寸限制register 变量的最大尺寸通常等于寄存器的大小通常是一个词或几个词。
2. 案例
尽管 register 关键字在现代C编程中不再被推荐使用但以下是一个简单的例子展示了如果编译器支持 register 关键字时它的用法
#include iostreamint main() {register int counter 0; // 建议编译器将counter存储在寄存器中for (int i 0; i 1000000; i) {counter; // 假设counter被存储在寄存器中这可能会提高循环的性能}std::cout Counter: counter std::endl;return 0;
}然而在现代C编译器中即使你使用了 register 关键字编译器也可能会忽略它因为它有自己的优化策略来决定哪些变量应该被存储在寄存器中。
3. 注意事项
废弃由于 register 关键字在C11中被废弃因此在新的C代码中应避免使用它。性能优化如果你关心性能并且想要优化特定变量的访问速度最好让编译器自己处理这些优化或者通过其他方式如使用更高效的算法或数据结构来提高程序性能。代码可读性使用 register 关键字可能会降低代码的可读性因为它引入了一个不再被现代编译器广泛支持的特性。
综上所述虽然 register 存储类在理论上可以用于优化变量访问速度但在现代C编程中它已经不再是一个有用的特性应该被避免使用。
四、static 存储类
在C中static存储类是一个非常重要的特性它用来控制变量的存储方式和可见性。以下是static存储类的详细解释
1. 局部静态变量
当static用于函数内的局部变量时它会使该变量具有静态存储期即变量在程序的整个执行期间都存在但只在定义它的作用域内可见。这样的变量只初始化一次每次函数调用时保留其最后一次使用的值。
特点
存储在静态存储区。只在定义它的函数内可见。即使函数调用结束也不会销毁。只初始化一次。
示例
#include iostream
using namespace std;void countFunction() {static int count 0; // 局部静态变量count;cout Count: count endl;
}int main() {countFunction(); // 输出 1countFunction(); // 输出 2countFunction(); // 输出 3return 0;
}2. 全局静态变量
在函数外部使用static声明的全局变量或函数会限制其链接性可见性使其只在定义它的文件中可见。这有助于避免不同源文件之间的命名冲突。
特点
存储在全局数据段。只在其定义的文件内可见。在程序的整个执行期间都存在。
示例
// file1.cpp
#include iostream
static int globalValue 10;
void display() {std::cout Global Value in file1: globalValue std::endl;
}// file2.cpp
// extern int globalValue; // 如果取消注释则会导致编译错误因为globalValue在file2中不可见// 假设有main函数在file2.cpp或其他地方
// 调用display函数会正常显示file1中的globalValue值3. 静态类成员变量
在类中声明static变量时该变量不属于任何特定的对象实例而是类的所有实例共享的。它们必须在类外部初始化。
特点
存储在静态存储区。类的所有对象共享同一份拷贝。初始化时使用作用域运算符(::)来标明它所属类。
示例
#include iostream
using namespace std;class MyClass {
public:static int staticValue;
};int MyClass::staticValue 0; // 初始化int main() {MyClass obj1, obj2;obj1.staticValue 5;cout Static Value from obj1: obj1.staticValue endl; // 输出 5cout Static Value from obj2: obj2.staticValue endl; // 输出 5return 0;
}4. 静态类成员函数
静态成员函数与静态数据成员一样都是类的内部实现属于类定义的一部分。它们可以在没有对象实例的情况下调用并且只能访问其类的静态成员。
特点
没有this指针。只能访问类的静态成员变量和静态成员函数。
示例
#include iostream
using namespace std;class MyClass {
public:static int staticValue;static void displayValue() {cout Static Value: staticValue endl;}
};int MyClass::staticValue 10;int main() {MyClass::displayValue(); // 输出 10return 0;
}五、extern 存储类
在C中extern关键字是一个非常重要的存储类修饰符它主要用于声明一个全局变量或函数这些变量或函数是在其他文件中定义的。下面详细解释extern存储类的用法和特点
1. 声明全局变量
当我们在多个文件中使用同一个全局变量时extern可以帮助我们实现这一目标。通过extern我们可以在一个文件中声明该全局变量而不是定义它即不分配内存然后在其他文件中使用它。这样做的好处是避免了在多个文件中重复定义同一个全局变量可能导致的编译错误。
示例
假设我们有两个文件main.cpp和support.cpp。
在support.cpp中定义一个全局变量
int testVar 0; // 定义一个全局变量在main.cpp中我们通过extern来声明这个全局变量以便使用它
extern int testVar; // 声明外部变量int main() {testVar 10; // 使用外部变量// ...
}2. 声明函数
除了变量之外extern也可以用于声明在其他文件中定义的函数。这样我们就可以在多个文件中调用同一个函数而不需要在每个文件中都重复函数的定义。
示例
在support.cpp中定义一个函数
void testFunc() {// 函数体
}在main.cpp中我们通过extern来声明这个函数以便调用它
extern void testFunc(); // 声明外部函数int main() {testFunc(); // 调用外部函数// ...
}然而需要注意的是在C中对于函数的声明extern通常是隐式的即我们不需要显式地写出extern来声明一个函数是在其他文件中定义的。但在某些特殊情况下为了强调或明确函数的外部链接性也可以显式地使用extern。
3. extern “C”
在C中我们还会经常看到extern C的用法。这是因为C支持函数重载而C语言不支持。当C代码需要被C语言代码调用时为了避免链接错误即C编译器对函数名进行名称修饰而C编译器不进行我们需要用extern C来告诉C编译器按照C语言的方式来链接这些函数。
示例
extern C void testFunc() {// 函数体
}或者在头文件中声明时
#ifdef __cplusplus
extern C {
#endifvoid testFunc();#ifdef __cplusplus
}
#endif4. 注意事项
extern只是声明不是定义。它告诉编译器变量或函数的类型但不会分配内存。extern声明的变量或函数必须在某个文件中被定义否则在链接阶段会报错。extern只能用于全局变量和函数不能用于函数内部的局部变量。在C中对于函数的声明extern通常是隐式的但对于全局变量或需要明确指出外部链接性的函数extern是必需的。
六、mutable 存储类
在C中mutable关键字是一个特殊的存储类修饰符它用于类的成员变量上以允许这个成员变量即使在常量成员函数即被const修饰的成员函数中也能被修改。这个特性在需要缓存计算结果、跟踪对象状态变化等场景中非常有用特别是当这些变化不应该影响对象的逻辑常量性时。
1. 基本用法
当你将mutable关键字应用于类的成员变量时这个成员变量即使在常量成员函数中也可以被修改。这意呀着常量成员函数即被声明为const的成员函数可以修改这个mutable成员变量的值而不会违反const成员函数不应修改任何成员变量的常规规则。
2. 示例
#include iostreamclass MyClass {
private:mutable int cache; // 使用mutable修饰允许在const成员函数中被修改int value;public:MyClass(int v) : value(v), cache(-1) {} // 初始化valuecache初始化为-1表示未计算// 常量成员函数但可以修改mutable成员变量cacheint getValue() const {if (cache -1) { // 如果cache未被计算cache computeValue(); // 计算并存储结果}return cache;}// 非const成员函数可以修改所有成员变量void setValue(int v) {value v;cache -1; // 重置cache因为value的改变可能影响计算结果}private:// 假设这是一个耗时的计算过程int computeValue() const {// ... 进行计算 ...return value * 2; // 仅为示例}
};int main() {MyClass obj(5);std::cout Value: obj.getValue() std::endl; // 输出Value: 10obj.setValue(10);std::cout New Value: obj.getValue() std::endl; // 输出New Value: 20return 0;
}3. 注意事项
mutable只能用于类的成员变量上。使用mutable要谨慎因为它可能会让代码的阅读者感到困惑特别是当常量成员函数实际上修改了某些状态时。因此建议只在确实需要时才使用mutable并且确保在文档中清楚地说明这一点。mutable成员变量通常用于缓存计算结果、状态跟踪等场景这些场景中的变化不会影响对象的逻辑常量性。mutable成员变量不会改变常量成员函数的const性质即常量成员函数仍然不能修改类的非mutable成员变量。
七、thread_local 存储类
在C11及以后的版本中thread_local关键字被引入作为一个存储类修饰符用于声明线程局部存储Thread-Local Storage, TLS的变量。这意味着每个线程都有该变量的一个独立实例线程之间对这些变量的修改是互不影响的。
1. 基本用法
当你在变量声明前加上thread_local关键字时这个变量就变成了线程局部的。每个线程对这个变量的访问都指向它自己的独立实例。
#include iostream
#include threadthread_local int tls_var 0; // 线程局部变量void increment() {tls_var; // 每个线程都会增加它自己的tls_var实例std::cout Thread std::this_thread::get_id() sees tls_var as tls_var std::endl;
}int main() {std::thread t1(increment);std::thread t2(increment);increment(); // 主线程也会执行t1.join();t2.join();return 0;
}在这个例子中每个线程包括主线程都会打印出它自己的tls_var值这些值在每次调用increment函数时都会递增但每个线程看到的都是它自己的独立副本。
2. 注意事项
thread_local变量在其所属线程开始时被构造在线程结束时被销毁。这意味着你不能在全局作用域中直接初始化一个需要构造函数和析构函数的thread_local变量因为全局变量的构造和析构发生在程序开始和结束时而这时线程可能还没有开始或已经结束。thread_local变量不会跨线程共享这意呀着每个线程对其的修改都不会影响到其他线程。在某些平台上thread_local变量的实现可能依赖于动态内存分配这可能会引入额外的性能开销。此外如果线程数量非常多可能会消耗大量的内存。在使用thread_local时要特别注意内存泄漏和异常安全问题。如果thread_local变量在析构时抛出异常并且这个异常没有被捕获那么程序可能会以未定义的方式终止。并不是所有的C实现都支持thread_local但它被C11标准所要求因此大多数现代编译器都支持它。
八、相关链接
Visual Studio Code下载地址Sublime Text下载地址「C系列」C简介、应用领域「C系列」C 基本语法「C系列」C 数据类型「C系列」C 变量类型「C系列」C 变量作用域「C系列」C 常量知识点-细致讲解「C系列」C 修饰符类型