sizeof关键字
在c语言中,sizeof并不是一个函数,而是一个单目运算符,如同c语言中的++,-- 一般。这一点我们一定要注意。sizeof的用法并不单一,sizeof(类型),这样的语法是可行的,或者直接是sizeof(4),或者sizeof(a)。
为了深刻理解sizeof是一个单目运算符号,看这样一段代码:
#include<iostream>
using namespace std;
int main(){
int a = 1;
int len_1 = sizeof(a);
int len_2 = sizeof(int);
int len_3 = sizeof a;
int len_4 = sizeof(4);
cout<<"len_1 = "<<len_1<<" "<<"len_2 = "<<len_2<<" "<<"len_3 = "<<len_3<<" "<<"len_4 = "<<len_4<<endl;
}
正是因为sizeof是一个单目运算符号,所以len_3定义也是成立的。
同样我们需要注意:sizeof只是一个运算符,在编译中就会把具体的值进行返回,所以在运行时候是并不会计算的,为了更加深刻理解这个代码,我们看这样一个例子。
#include<iostream>
using namespace std;
int main(){
int a = 2;
int b = sizeof(++a);
cout<<"a = "<<a<<" "<<"b = "<<b<<endl;
return 0;
}
这段代码的输出应该是a = 2 b = 3。那么很容易产生这样的疑问,a的值为什么不是3呢,代码中明明把a自加了一次。这也就是我们强调的原因,sizeof在编译过程中,就把sizeof(a++)之类的代码直接便以为sizeof(int)这样。执行的代码当然和a没有关系,所以a的值并不会产生变化。
static变量
前面我们提到过,被static修饰的静态变量会放于data区,那么static的作用到底是什么呢?
static会改变数据的生存周期。我们举这样一个例子:
#incldue<iostream>
using namespace std;
int Func(){
static int c = 0;
++c;
return c;
}
int main(){
int c = Func();
int d = Func();
cout<<"c = "<<c<<" "<<"d = "<<d<<endl;
return 0;
}
被static修饰的局部变量并不会放入stack区,而是放入了data区。并且只会有一次初始化,当程序再一次调用这个函数的时候,static变量并不会经过第二次初始化,而是直接跳过初始化代码执行。所以上面代码执行的结果应该是c = 1,d = 2。
下面看这样一段十分具有迷惑性的代码(c99标准下可以通过,c11不可以)
#include<iostream>
using namespace std;
void func(int x){
static int a = 0;
static int b = x;
++a;
++b;
cout<<"a = "<<a<<" "<< "b = "<<b<<endl;
}
int main(){
for (int i = 10; i > 0; --i){
func(1);
}
return 0;
}
代码运行图:
当调用函数func时候,系统会给func分配相应的栈帧空间,但是由于func定义的两个变量均为static变量,所以并不会存放到栈帧当中,而是在data区进行了初始化。所以当函数生命周期结束,栈帧被释放的时候,并不会影响这两个static的值的生命周期。当下一次调用函数时候,也不会进一步的初始化,而是跳过初始化代码。那么究竟如何实现这个原理呢?
下面我们看这样一段代码:
#include<stdio.h>
int main()
{
int initNum = 3;
for (int i=5; i > 0; --i)
{
static int n1 = initNum;
//我们在这里增加了两句代码,把n1所指的内存地址后面4个字节赋值成0
int* p = &n1;
p++;
*p = 0;
//end
n1++;
printf("%d\n", n1);
}
getchar();
return 0;
}
这里我们只是举个例子,因为每个编译器的编辑地点都是不同的,当我们经过一次初始化以后,对于编译器而言,会在data区这个static变量附近的区域做一个标记,用来标记当前变量已经经过一次初始化,并且不会经历第二次初始化。实际上我们可以这样说,不管是什么样的变量,在他的生命周期当中,他都只能经历一次初始化,有人会问那为什么stack区域变量可以一直初始化,这里我们一定要明白,当经历过一次函数调用以后,里面的变量已经结束了其生命周期被释放,第二次调用生成的是新的变量,尽管变量名是相同的,这一点我们一定要注意。
对之前程序的升级
学完了static局部变量,我们回头再看我们之前写过的年月日函数,我们不经的提出了这样的问题,当时我们定义的YearMonthDay_to_total()函数,会循环调用YearMonth_to_day()这个函数,所以这个函数中的数组也会被不停的初始化,这样的做法其实是十分不合理的,对内存开销也十分不好,所以我们应该把days数组定义为static变量。再还有,既然我们对于这个数组的操作仅限于访问其中的内部数据,那么就应该把它定义为const类型的常变量,这样同样增加了程序数据的安全性。
最后我们思考最后一个这样的问题,既然我们可以用列表法加载月份,那么我们是否也可以用同样的方法在程序开始前,就把每个月最后一天是当年第几天这个列表加载出来呢?这样同样可以增加代码的可读性等。
我们看一下升级以后的代码:
#include<stdio.h>
#include<stdbool.h>
bool Is_LeapYear(int year){
return (year % 4 == 0 && year % 100 != 0) || year % 400 == 0;
}
int YearMonth_to_day(int year, int month){
const static int days[13] = {29, 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31};
if (Is_LeapYear(year) && 2 == month){
month = 0;
}
return days[month];
}
int YearMonthDay_to_total(int year, int month, int day){
const static int totals[14] = {0, 0, 31, 59, 90, 120, 151, 181, 212, 243, 273, 304, 334, 365};
if(month > 2 && Is_LeapYear(year)){
return totals[month] + day + 1;
}else{
return totals[month] + day;
}
}
bool judge(int year, int month, int day){
if(year <= 0){
printf("请输入公元后的年份 \n");
return false;
}
if (month <=0 || month >= 13){
printf("请输入正确的年份 \n");
return false;
}
if (!(day <= YearMonth_to_day(year, month))){
printf("没有这个日期 \n");
return false;
}
return true;
}
void operator(){
int year, month, day;
bool tag = true;
while(1){
printf("请输入年月日 \n");
//char c = getchar();
char c;
scanf("%d %d %d",&year, &month, &day);
tag = judge(year, month, day);
if (tag){
if(Is_LeapYear(year)){
printf("润年 \n");
}else{
printf("非润年 \n");
}
printf("这个月有%d天\n",YearMonth_to_day(year, month));
printf("这是这一年的第%d天 \n",YearMonthDay_to_total(year, month, day));
}
printf("是否继续Y/N \n");
c = getchar();
scanf("%c",&c);
if(!('y' == c || 'Y' == c)){
break;
}
}
}
int main(){
operator();
return 0;
}