C++中变量声明顺序的不同导致不同的运行效果嘛?

博客介绍了C++实现凯撒密码加密时的有趣现象,输入移动位数n和四个字符,按原代码运行字符无变化。经分析,是数组c和变量n内存地址相邻,输入字符串时编译器添加的NULL符覆盖了n的值。交换变量声明顺序后程序有效果,但数组仍可能影响其他内存,提醒注意数组长度。

输入移动位数n,然后接受用户输入的四个字符,每个字符向后移动n位实现凯撒密码加密。

例如,输入n为3,再输入abcd,输出defg。只考虑小写字母的情况下,部分代码如下:

    int n;
    char c[4];
   
    cin>>n;
    cin>>c;
    
    for(int i=0;i<4;i++)
    {
      printf("%c",(c[i]+n-97)%26+97);
    }

当代码运行后发现输入:

3
abcd

输出的内容依然是:

abcd

更好玩的事情是,当把源代码中声明变量的顺序调整一下,情况就发生了变化:

char c[4];    
int n;
    
cin>>n;
cin>>c;
for(int i=0;i<4;i++)
{
   printf("%c",(c[i]+n-97)%26+97);
}

运行程序,输入:

3
abcd

输出的内容就变为:

defg

这个代码在不同的机器上应该有不同的运行效果,但是在我的机器上确实发生了这个好玩的情况。存在即合理,到底发生了什么呢?

其实打印一下变量n和c的地址就知道事情的真相了:

int n;
char c[4];

cout<<&n<<endl;
cout<<&numbers<<endl;

cin>>n;
cin>>c;

n和c的地址分别是:

0x28fef8
0x28fef4

可以看见恰好数组c和变量n的内存地址居然是挨在一起的。

当执行到cin>>n的时候,输入3,在0x26fef8中存入数字3。

回车后执行cin>>c,输入abcd。因为c是数组的名称,所以cin会认为输入的abcd是一个字符串,于是编译器会自动在abcd后面补上一个NULL符,NULL符的ASCII码值为0,一般写作'\0'。所以接下来发生的事情就是:

C++把0当做写入输入在第五个元素即c[4],所以它的写入位置就是数组首地址(即a[0]的地址)偏移4位,这个地址恰好是变量n的地址,所以0被写入到了变量n中。

后续在做字符变化的时候,所有的字符都是与0在做加法运算,因此字符没有产生任何变化。

当交换了变量的声明顺序后,数组c和变量n的地址发生了变化:

char c[4];
int n;

cout<<&n<<endl;
cout<<&c<<endl;

cin>>n;
cin>>c;

此时n和c的地址如下:

0x28fef4
0x28fef8

于是再输入n和abcd的时候内存变为了如下形式:

此时从内存上看,程序中需要的变量n和c[0]c[1]c[2]c[3]都有需要的值,因此程序产生了效果。但是要注意数组依然影响了“无辜的”内存地址0x28fefc。 

所以,如果以字符串的形式进行字符数组内容的初始化,必须要让数组的长度至少比字符串字面内容多1位,用来存储编译器添加的NULL结束字符,防止影响到“无辜的”内存空间,造成意想不到的错误。

int n;
char c[5];

//cout<<&n<<endl;
//cout<<&c<<endl;

cin>>n;
cin>>c;
for(int i=0;i<4;i++)
{
    printf("%c",(c[i]+n-97)%26+97);
}

 

### 静态成员变量的定义与存储机制 在 C++ 中,静态成员变量属于整个类,而不是类的某个实例。这意味着无论创建多少个类的对象,静态成员变量都只有一个副本,并且其生命周期贯穿整个程序运行过程[^3]。由于静态成员变量是类级别的变量,因此它的定义和初始化不能直接嵌入到类内部,而必须在类外部进行。 这一设计主要出于以下几方面的原因: --- ### 1. 防止多重定义错误 如果允许在类内部对静态成员变量进行初始化,则在多个源文件中包含该类的头文件时,每个翻译单元都会生成一个独立的初始化代码段。这将导致链接阶段出现“多重定义”的错误。例如,若 `MyClass` 的头文件被多个 `.cpp` 文件包含,那么每个 `.cpp` 文件都将尝试定义并初始化同一个静态变量 `staticVar`,从而违反了“一个定义规则”(ODR)[^2]。 为避免此类问题,C++ 要求静态成员变量必须在类外单独定义一次。这样可以确保它在整个程序中只被定义一次,从而避免链接冲突。 ```cpp // MyClass.h class MyClass { public: static int staticVar; // 声明 }; // MyClass.cpp int MyClass::staticVar = 0; // 定义和初始化 ``` --- ### 2. 存储区与链接属性 静态成员变量的存储方式与全局变量类似,通常位于程序的 `.data` 或 `.bss` 段中,其生命周期从程序启动开始,直到程序结束为止[^3]。这种全局性质决定了它需要在类之外显式地分配存储空间。 此外,静态成员变量默认具有外部链接属性(external linkage),这意味着它可以被多个翻译单元访问。但如果在类内直接定义静态变量,编译器会将其视为具有内部链接(internal linkage),这会导致链接器无法找到其定义,进而报错[^4]。 --- ### 3. 初始化时机与控制 类内部的声明只是告诉编译器该变量的存在,并不为其分配存储空间。真正的定义和初始化必须在类外完成,以明确其内存布局和初始化顺序。对于某些复杂类型或需要运行时计算的初始化值,这种机制也提供了更灵活的控制能力。 例如: ```cpp class Counter { public: static int count; }; int Counter::count = initCounter(); // 可调用函数进行初始化 int initCounter() { return getInitialValue(); // 动态获取初始值 } ``` --- ### 4. 类模板中的特殊处理 对于类模板中的静态成员变量,其定义同样必须放在类模板外部。这是因为模板的实例化发生在编译期,每个实例化的类都需要拥有自己独立的静态变量副本。如果不强制要求类外定义,将可能导致模板实例之间共享同一份静态变量,造成语义上的歧义和错误。 --- 综上所述,C++ 中静态成员变量必须在类外定义的设计,是为了保证程序的正确性、避免多重定义错误、控制链接属性以及提供灵活的初始化机制。这一机制确保了静态成员变量作为类级别资源的唯一性和可访问性。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值