static
static修饰局部变量
static修饰局部变量用于修改变量的存储位置,从自动变量修改为静态变量(在静态区开辟空间,不在栈上开辟),但变量的链接属性和作用域不受影响。static修饰局部变量时该变量在程序执行之前创建,并在程序的整个执行空间一直存在,即使出了作用域也只是被隐藏并不销毁该变量。请看下面的两段代码
代码一:没有static修饰的局部变量
int main()
{
int i=0;
for(i=0;i<10;i++)
{
int j=10;
j+=1;
printf("%d ",j); //没有static修饰的j每次都是10
}
system("pause");
return 0;
}
代码二:static修饰的局部变量
int main()
{
int i=0;
for(i=0;i<10;i++)
{
static int j=10;
j+=1;
printf("%d ",j); //有static修饰的j每次都是上一次+1之后的值
}
system("pause");
return 0;
}
从上面两段代码可以看出static修饰局部变量确实改变了变量的存储位置,存储在静态区上,出了作用域也未被销毁,所以代码二的j每次都是加1之后的值
static修饰全局变量和函数
static修饰函数和全局变量时用于修改变量的链接属性,由extern变为internal,但是变量的存储类型和作用域不受影响,且static修饰的全局变量只能在他们的源文件中访问,即使外部文件使用extern也无法访问
好了static的基础介绍就到这里了,下面让我们来看一道关于static的例题吧
int count=3;
int main()
{
int i=0,sum=0,count=2;
for(i=0,sum=0;i
static int count=4;
count++;
if(i%2 == 0)
{
extern int count;
count++;
sum+=count;
}
sum+=count;
}
printf("%d %dn",count,sum); //4 20
system("pause");
return 0;
}
刚一看到这道题是不是有点晕呢?确实刚开始的时候我也觉得有点害怕,因为里面存在三种不同存储方式的变量count:全局的,局部的,静态的,关键是它还时不时的使用一下count这就容易让人搞不清楚到底每次使用的时候调用的是哪一个count,不过不用怕,让我们来慢慢分析
注意:
1).当局部变量和全局变量冲突的时候,局部变量优先,当全局的count和局部的count同时出现时优先考虑局部的变量count
2).extern声明变量使该变量具有外部链接属性
3).static修饰局部变量(不销毁该变量出作用域被隐藏)
const
为什仫要存在const关键字?以及const可以修饰什仫?用const修饰有什仫与众不同的地方?下文主要就这三个问题予以讨论。
要解决第一个问题就得了解define定义的宏和标识符了,详情请见下面链接
大概了解了define定义的标识符和宏之后,我们就会发现由define定义的宏的很多缺点:宏不可以调试;宏是直接替换的,当替换较多时浪费空间;宏虽然可以传类型但宏本身是无类型的;宏是带有副作用的;...而这些缺点正好可以由const来解决。从下面一段代码中就可以看出
#define MAX 5 //define定义的标识符
const int max=10 //此时未将Max放入内存中而是放到符号表中
int main()
{
int i=MAX; //预编译期间进行标识符的替换
int j=max; //此时为j分配内存,以后不再分配
int m=MAX; //再进行一次宏的替换,再分配内存
int n=max; //不再分内存配
因为编译器通常不为普通const只读变量分配存储空间,而是将他们保存在符号表中,这就使该变量成为一个编译期间的值,没有了存储与读内存的操作,从而使得效率较高。这就解决了为什仫在C++中以const来代替define定义的标识符和宏了吧
const修饰变量
const修饰变量时const可以在类型的左边也可以在类型的右边,比如
const int a=10;
int const a=10;
在C语言中const修饰变量使得该变量具有常性但依然是个变量(在C中const修饰的变量是常变量)
但是在C++中为了消除宏的缺点同时也需要定义常量,此时const修饰的变量去除了它在C中的变量的属性,完全升级为一个具有常性的量(在C++中const修饰的变量是常量)比如下列代码:
const int a=10;
int arr[a]={0};
数组的长度必须是一个代表常量的值,如果在.cpp下可以运行就可以说明在C++中const修饰的变量是否具有常性了;上述代码在.c文件中是不可运行的,在.cpp下可以运行的,这就说明在C++中const修饰的变量已经不具有变量的性质了,完全是一个常量
const修饰指针
const修饰一级指针
1).const在*的左边
int a=10;
int b=20;
const int *pa=&a;
*pa=20; //错误
a=20;
pa=&b;
const修饰的是*pa,说明指针pa所指向的内容不可以修改,但可以通过pa指针所指向的变量修改也可以改变指针变量本身;
2).const在*的右边
int a=10;
int b=20;
int *const pa=&a;
pa=&b;//(错误)
*pa=b;
const修饰的是pa,说明指针变量本身是不可以被修改的,但可以修改指针所指向的内容;
3).const即在*的左边也在*的右边
int a=10;
int b=20;
const int *const pa=&a;
pa=&b;//(错误)
*pa=b;//(错误)
此时const即修饰*pa也修饰pa,说明指针所指向的内容是不可以修改的,指针变量本身也是不能被修改的
帮助记忆的小窍门:
如果我们把pa和a看作男女朋友关系,女生a可以换男朋友看作是可以修改变量本身的地址,女生a可以花男朋友的钱类似可以修改变量的内容;const在*的左边就类似这个女生a是可以换男朋友的但是不可以花男朋友的钱,const在*的右边就类似这个女生a是可以花男朋友钱的但是不可以换男朋友,const如果即在*的左边也在*的右边那这个女生可就惨了,即不可以换男朋友也不可以花男朋友的钱,是不是感觉很有意思呢?
const修饰二级指针
1).const在第一个*的左边
int a=10;
int b=20;
int *pa=&a;
int *pb=&b;
const int **ppa=&pa;
**ppa=b;//(错误)
ppa=&pb;
*ppa=&b;
a=40;
指向关系如下图:
const修饰在第一个*的左边,去掉类型我们发现它修饰的是**pa,说明我们此时是不可以通过指向a这块空间的二级指针ppa来修改a的值的,但是可以通过a来改变这块空间的内容,也可以修改ppa指针变量本身
2).const放在两个*的中间
int a=10;
int b=20;
int *pa=&a;
int *pb=&b;
int *const *ppa=&pa;
ppa=&pb;
*ppa=&b; //(错误)
**ppa=b;
const修饰在两个*的中间,根据就近原则,我们知道const修饰的是*ppa,说明此时我们不可以改变ppa所指向的空间pa的内容的,但是我们可以修改ppa指针变量的本身,也可以修改二级指针ppa所指向空间a的值
3).const 放在第二个*的右边
int a=10;
int b=20;
int *pa=&a;
int *pb=&b;
int **const ppa=&pa;
ppa=&pb; //(错误)
*ppa=&b;
**ppa=b;
const放在第二个*的右边,根据就近原则,我们知道const修饰的是ppa,说明此时我们是不可以修改二级指针ppa指针变量本身的地址,我们可以修改二级指针ppa所指向空间pa和pa所指向空间a的内容
const修饰二级指针还有很多情况,上述只讨论了只有一个const修饰的二级指针,只要把握要点,清楚const修饰的是什仫?二级指针本身?二级指针指向一级指针的内容?二级指针指向一级指针的地址?二级指针间接指向变量的内容?只要清楚了这些就可以很好的驾驭const修饰指针的问题了。
const修饰引用
1).const修饰引用即变量的别名,该别名是没有权限修改该变量的内容的,只能通过该变量修改内容
int a=10;
const int &refa=a;
refa=20; //(错误)
a=20;
2).const修饰变量的权限是可以缩小的但不可以扩大
const int a=10;
int &refa=a; //(错误)
int b=20;
const int &refb=b;
3).const修饰全局变量,该全局变量存放在只读数据区,即使使用指针也无法修改该变量;定义时不一定分配空间在使用时才分配空间
const int n=10; //const修饰全局变量
int main()
{
int arr[n]; //只要说明n为常量也未分配空间
const int *p=&n; //此时才为n分配空间
system("pause");
return 0;
}
将const修饰的全局变量的地址给指针也就是使用时才分配地址
4).常量具有常性,只有常引用可以引用常量
int &refa=5; //(错误)
const int &refb=5;
5).引用的类型不匹配
double d1=3.1;
const int &refd1=d1; //3
d1=6.95;
注意:
1).在内存中为变量分配空间时是存在一个临时变量的,而这个临时变量是具有常性的(默认类型为const int)
2).当变量本身的名字和别名的类型不符合时,引用的就不是d1了而是这个临时的变量
3).既然是个临时的变量使用完后就会销毁,所以当再修改变量d1的值也不会影响refd1的值
6).const修饰函数的参数,修饰函数的返回值:
当希望在函数内部不可以随意修改该变量的值时可以给函数的参数加上const修饰
const也可以修饰函数的返回值,此时函数的返回值是不可被修改的或者当确实需要返回一个常性的值时可以用const修饰函数的返回值,比如类的六个默认的成员函数的拷贝构造函数就是传常引用,以日期类为例的拷贝构造函数
Date(const Date& d)
:_year(d._year)
,_month(d._month)
,_day(d._day)
{}
const修饰成员函数
const修饰成员函数,在成员函数后面加const,const修饰this指针所指向的对象,也就是保证在调用这个成员函数的对象在函数内不会被改变
以日期类为例观察const修饰成员函数时的具体传值过程
日期类
#define _CRT_SECURE_NO_WARNINGS 1
#include
using namespace std;
class Date
{
public:
Date(int year,int month,int day)
:_year(year)
,_month(month)
,_day(day)
{}
void Display()const
{
cout<<_year<<"-"<<_month<<"-"<<_day<
void Display()
{
cout<<_year<<"-"<<_month<<"-"<<_day<
private:
int _year;
int _month;
int _day;
};
int main()
{
Date d1(2016,8,10);
d1.Display();
const Date d2(d1);
d2.Display();
system("pause");
return 0;
}
此时也就存在了下面几个问题,这也是我们之前提到的const修饰变量的权限问题(权限可以缩小但不可以扩大)
1).const对象不可以调用非const成员函数,可以调用const成员函数
2).非const对象可以调用非const的成员函数,也可以调用const成员函数
3).const成员函数可以调用其他的const成员函数,不可以调用非const的成员函数
4).非const成员函数内可以调用其他的成员函数,也可以调用非const成员函数