类内初始化
class Person{
public:
// static member shared by all obj.
// memory was allocated during compilation phase
static int m_a = 0;
//error:a member with an in-class initializer must be const
int a = 20;
};
why the member “int a” in class can be init,but the static member "static int m_a"can’t be init in class?
Great question!
In C++, non-static data members (likeint a = 20;) can be initialized directly in the class definition (sinceC++11). This is calledin-class member initialization.
However, static data members (likestatic int m_a;) are shared by all instances of the class and exist independently of any object.
They must be defined and initialized outside the class (in a source file), because:
- The class definition only declares the static member.
- The actual storage for the static member must be allocated in exactly one translation unit (usually the
.cppfile).
静态成员变量,类内(只是)声明,类外(才)定义
1. 什么是 Translation Unit(翻译单元)?
Translation Unit(翻译单元) 是 C/C++ 编译过程中的一个基本概念。
它指的是编译器实际编译的源代码单元,由以下部分组成:
- 一个源文件(通常是
.c、.cpp文件) - 该源文件中包含的所有头文件(通过
#include指令展开后的内容) - 以及所有宏展开和预处理后的内容
换句话说,一个源文件连同它所有直接或间接包含的头文件,经过预处理后形成的整体,就是一个翻译单元。
2. 为什么需要“一个翻译单元”来分配静态成员变量的存储?
对于类的静态成员变量,声明通常写在类定义里(头文件中的 static 声明),
但静态成员变量属于类的全局共享变量,必须在某个翻译单元中定义一次, 以分配实际的内存。
如果你在多个翻译单元中都定义了静态成员变量,链接时会报重复定义错误;
如果没有定义,会报链接错误。
3. 举例说明
示例:
头文件 MyClass.h:
#ifndef MYCLASS_H
#define MYCLASS_H
class MyClass {
public:
static int count; // 声明,不分配内存
MyClass() { ++count; }
};
#endif
源文件 MyClass.cpp:
#include "MyClass.h"
int MyClass::count = 0; // 定义并分配内存,属于这个翻译单元
其他文件 main.cpp:
#include <iostream>
#include "MyClass.h"
int main() {
MyClass a;
MyClass b;
std::cout << MyClass::count << std::endl; // 输出 2
return 0;
}
MyClass.h只是声明了static int count,没有分配内存。MyClass.cpp对count进行了定义和初始化,分配了实际内存。- 编译时,
MyClass.cpp和main.cpp各自是一个翻译单元。 - 链接时,
count的内存只从MyClass.cpp这一个翻译单元分配,避免重复定义。
4. 总结
| 术语 | 说明 |
|---|---|
| 翻译单元 (TU) | 预处理后,一个源文件及其包含的所有头文件组成的整体 |
| 静态成员变量的定义 | 必须放在某一个翻译单元中,进行实际内存分配 |
| 多个翻译单元 | 共享同一个静态成员变量的定义,链接器负责合并 |
5. 拓展
- C++17 引入了
inline static变量,可以放在头文件里,允许在多个翻译单元中定义而不冲突,编译器和链接器负责合并。 - 传统写法仍然需要在一个
.cpp文件中定义静态成员变量。
空类的大小
// sizeof($empty_class) = 1
// each object should have a unique address in memory.
class Myclass
{
};
void test01()
{
std::cout<<"sizeof(Myclass):"<<sizeof(Myclass)<<"\t(Empty class)\n";
Myclass myclass;
std::cout<<"sizeof(myclass):"<<sizeof(myclass)<<"\t(obj of Empty class)\n";
Myclass arr[3];
}
空类(的对象)的大小为 1 byte(上述代码结果如下)
sizeof(Myclass):1 (Empty class)
sizeof(myclass):1 (obj of Empty class)
每个对象都应该有一个独一无二的内存地址

上图中,空类构造的数组中成员的地址如下:
&arr[0] = 0x7fffffffd435
&arr[1] = 0x7fffffffd435
&arr[2] = 0x7fffffffd437
类对象模型
static成员并不属于类的对象
eg1.
class Myclass
{
public:
int m_a;
// static int m_s;
};
// int Myclass::m_s = 0;
void test01()
{
std::cout<<"sizeof(Myclass):"<<sizeof(Myclass)<<"\t(class)\n";
Myclass myclass;
std::cout<<"sizeof(myclass):"<<sizeof(myclass)<<"\t(obj of class)\n";
}
sizeof(Myclass):4 (class)
sizeof(myclass):4 (obj of class)
eg2.
class Myclass
{
public:
int m_a;
static int m_s;
};
int Myclass::m_s = 0;
void test01()
{
std::cout<<"sizeof(Myclass):"<<sizeof(Myclass)<<"\t(class)\n";
Myclass myclass;
std::cout<<"sizeof(myclass):"<<sizeof(myclass)<<"\t(obj of class)\n";
}
sizeof(Myclass):4 (class)
sizeof(myclass):4 (obj of class)
虽然新增了static int的成员,但是类/对象的大小未变/依然是4
接下来查看内存布局
#include <iostream>
class Myclass
{
public:
int m_a;
char m_b;
int m_c;
char * str = "abcd.";
double m_d;
static int m_s;
void func()
{
int local = 0;
}
};
int Myclass::m_s = 0;
void test01()
{
std::cout<<"sizeof(Myclass):"<<sizeof(Myclass)<<"\t(class)\n";
Myclass myclass;
std::cout<<"sizeof(myclass):"<<sizeof(myclass)<<"\t(obj of class)\n";
}
int main()
{
test01();
return EXIT_SUCCESS;
}
调试查看成员的地址:

WATCH中的值为:
1. &myclass = 0x7fffffffdf410
2. &myclass.m_a = 0x7fffffffdf410
3. &myclass.m_b = 0x7fffffffdf414 "cxx."
4. &myclass.m_c = 0x7fffffffdf418
5. &myclass.str = 0x7fffffffdf420
6. &myclass.m_d = 0x7fffffffdf428
7. &myclass.m_s = 0x55555558154 <Myclass::m_s>
从地址&myclass.m_s = 0x55555558154可以看出,static类型的成员是放在静态区的,和其他成员变量的地址有明显的区别
类对象的声明
对于以下的Person类:
class Person{
public:
Person()
{
std::cout<<"call Person()\n";
}
Person(int age):age(0)
{
std::cout<<"call Person(int age)\n";
this->age = age;
}
Person(const Person &person)
{
std::cout<<"call Person(cosnt Person&person)\n";
age = person.age;
}
int age;
};
Person p1 = Person(18);
//这个语句是如何工作的?是先调用有参构造构造出一个匿名的对象吗?然后再调用重载的赋值运算符,将这个匿名对象赋值给p1?
//会不会涉及到拷贝构造函数呢?
//还是说编译器根本没有构造一个匿名对象,而是直接把这个语句当做 “Person p1(18);”?
//又或者是这样做的但是编译器进行了优化?
一、这句代码的标准含义
Person p1 = Person(18);这是**拷贝初始化(copy initialization)**的语法。
- 先调用
Person(int)构造函数,构造一个匿名的临时对象。- 然后使用该匿名对象调用拷贝构造函数初始化
p1。- 注意这里不是赋值运算符,是拷贝构造函数,因为
p1是新声明的对象。
二、优化情况(返回值优化)
现代编译器通常会执行返回值优化(RVO)或拷贝消除(copy elision),
这导致实际上不会生成临时对象,而是直接构造p1。
因此:
- 编译器可能直接用
Person(int)构造函数初始化p1,- 不调用拷贝构造函数,
- 也没有匿名对象拷贝过程,效率更高。
三、总结
| 步骤 | 发生与否(标准情况) | 现代编译器优化后 |
|---|---|---|
| 调用有参构造函数 | 是 | 是 |
| 构造匿名临时对象 | 是 | 否 |
| 调用拷贝构造函数 | 是 | 否 |
| 调用赋值运算符 | 否 | 否 |
四、不同声明对象方式举例和详细分析
赋值法(先声明,后赋值)
Person p4; p4 = Person(18);
- 先调用默认构造函数
Person()构造p4。- 调用有参构造函数构造匿名临时对象
Person(18)。- 调用重载的赋值运算符进行赋值(注意你的类没有自定义赋值运算符,会用编译器默认的)。
- 最终
p4的内容为临时对象的内容。- 输出:
call Person() call Person(int age)统一初始化列表法(C++11)
Person p5{18};
- 直接调用
Person(int)构造函数初始化p5。- 不涉及临时对象、拷贝构造。
- 输出:
call Person(int age)隐式转换法
void foo(Person p) { } foo(18); // 18 隐式转换成 Person(18)
- 调用
Person(int)构造临时匿名对象传参。- 可能调用拷贝构造函数拷贝参数,或者优化消除。
- 输出:
call Person(int age) call Person(copy constructor) // 可能调用,也可能优化

7万+

被折叠的 条评论
为什么被折叠?



