c++ const总结

一:const和变量

1.一个非const对象的指针指向一个常量对象,即const对象的地址只能赋值给一个指向const对象的指针(非const对象的地址可以被赋值给指向const对象的指针)

const int a = 10;
int *b1 = &a; (错误)
int * const b2 = &a; (错误)
int const * b3 = &a; (正确)

int *b1 = &a;
会报错 invalid conversion from ‘const int*’ to ‘int*’ [-fpermissive]
int * const b2 = &a;
会报错 invalid conversion from ‘const int*’ to ‘int*’ [-fpermissive],这是因为const在*后边,修饰的是b2,说明b2是常量,即b2一旦赋值则不可更改
int const * b3 = &a;
const在指针前面表示b3指针指向的对象是常量。

2.常量折叠

#define PI 3.14
int main()
{
    const int r = 10;
    int p = pI; //这里会在预编译阶段产生宏替换,PI直接替换为3.14,其实就是int p = 3.14;
    int len = 2*r; //这里会发生常量折叠,也就是对常量r的引用会替换成他对应的值,相当于int len = 2*10;
    return 0;
}

如上述代码中所述,常量折叠表面上的效果和宏替换是一样的,只是,“效果上是一样的”,

而两者真正的区别在于:

a 宏是字符常量,在预编译完宏替换完成后,该宏名字会消失,所有对宏如PI的引用已经全部被替换为它所对应的值,编译器当然没有必要再维护这个符号。

b 而常量折叠发生的情况是,对常量的引用全部替换为该常量如r的值,但是,常量名r并不会消失,编译器会把他放入到符号表中,同时,会为该变量分配空间,栈空间或者全局空间

下面的代码能更清楚的体现出常量折叠

int main()
{
    const int i=0;         //定义常量i
    int *j = (int *) &i;   //看到这里能对i进行取值,判断i必然后自己的内存空间
                //请注意,int *j = &i;是不对的,前面第1点已经说明

    *j=1;                  //对j指向的内存进行修改

    printf("%d\n%d\n%d\n%d\n",&i,j,i,*j); //观看实验效果
    return 0;
}

上面的代码会输出(指针地址每次执行都不一样):
0012ff7c
0012ff7c
0
1
上面的输出说明两点:

  1. i和j地址相同,指向同一块空间,i虽然是可折叠常量,但是,i确实有自己的空间

  2. i和j指向同一块内存,但是*j = 1对内存进行修改后,按道理来说,*j==1,i也应该等于1,而实验结果确实i实实在在的等于0,这是为什么呢,就是本文所说的内容,i是可折叠常量,在编译阶段对i的引用已经别替换为i的值了,也就是说
    printf("%d\n%d\n%d\n%d\n",&i,j,i,*j)
    中的i已经被替换,其实已经被改为
    printf("%d\n%d\n%d\n%d\n",&i,j,0,*j)

初始化

1.const全局变量必须在声明的时候初始化
2.类const 成员变量必须在构造函数的初始化列表中初始化
3.类里的const数组因为无法在构造函数的初始化列表中初始化,则必须声明为static,在类体外初始化

作用域

c++中const全局变量的作用域默认是LOCAL的,即只在本文件中有效(c语言中const变量作用域默认是GLOBAL的),c++中跟static关键字的作用类似。
但是const全局变量也可以跟extern一起声明该变量可以用在其他的模块中
需要被其他文件使用的const对象定义成:

extern const int a = 10;

而需要使用这个const对象的地方声明成:

extern const int a;

const与指针

常量指针

const int a = 10;
const int *b = &a;
int const *b = &a;

第二行和第三行等价。常量是形容词,指针是名词,因此本质是指针,表示指向常量的指针,不能靠解引用改变它指向的对象的值,以此保护它所指向的常量的常量性。即*b=100;是错误的,编译器即会报错。但是b = &c是可以的,即指针本身的值是可以改变的。另外指针常量也可以指向变量,即a可以声明为int a = 10;b=&a;

指针常量
int a = 10;
int * const b = &a;

指针是形容词,常量是名字,因此这表示是一个常量,但是是指向指针的常量,因此b必须在声明时初始化,或者在类的构造函数中的初始化列表中初始化。b = &c;表示错误,*b =20;这个是可以的。 另外指针常量指向的必须是变量,即a必须是变量,因为可以通过b的解引用来改变a的值,如果a是常量,则存在矛盾。

指向常量的常指针
const int a = 10;
const int * const b = &a;

有两层含义,第一指针指向的是常量,不可以通过解引用更改内容;第二指针本身是常量,即必须在声明时初始化,后续不可更改指向的地址。

const与对象

常量对象的“常量性”是在构造函数完成了对象的初始化之后生效的,持续到调用了对象的析构函数。
成员函数没有修改对象的事实,并不足以表明它就是常量函数,常量函数必须显式地用const声明。
当const修饰对象时,编译器需要保证对象的数据成员在对象常量性保持期间不可改变。const对象可以访问成员变量,但是不可以修改成员变量的值。另外只能调用const成员函数。

二:const和函数

1.非成员函数不能有CV(const和volatile)限定符,静态成员函数也不能有CV限定符
2.const对象只能访问const成员函数
当一个对象调用其成员函数时,它隐含了一个形参this指针。比如我们定义一个类

class People {
private:
    string name;
public:
    string getName() const;
};

const对象为什么不可以访问非const成员函数呢?
实际上在编译器中getName变成People::getName(People *const this),该this指针指向的内容是可以改变的,但是该this指针不可以被改变。当我们用People对象调用getName函数时,如people.getName()时,编译器会解释成getName(&people),所以在getName中就可以使用this指针来改变对象people的成员变量了。
那么当对象People定义为const People people;这个表示people的内容是不可以改变的,当将&people作为一个参数传给形参this时,就要矛盾发生了,people是一个常量,其成员不可以改变,this指针的成员变量是可以改变的。如果可以将people的地址传给this指针,那么people这个常量的值就可以在this中被改变了,编译器不允许这种矛盾。因此const对象不能访问非const成员函数。
另外为什么const对象可以访问const成员函数呢?
这是因为const成员函数在编译器中解释为getName(const People * const this),两个const修饰,第二个const表示this不可以指向其他地址,第一个const是指this指向的内容不可改变,所以前面的矛盾就没有了,因此const对象可以访问const成员函数。
3. 函数重载:当const修饰函数体的时候,可以重载,根据调用者是否是const对象来决定调用哪个函数;当const修饰函数参数,并且函数参数是指针或引用的时候,可以重载,此时若传入的参数是const类型,则调用参数是const的函数,若传入的参数是非const参数,则调用参数是非const的函数。
const修饰函数体的函数重载

class P {
public:
    void print() const;
    void print();
};
const P p1;
p1.print();//调用const修饰的函数
P p2;
p2.print();//调用非const修饰的函数

const修饰指针函数参数的函数重载

void print(const int *a);
void print(int *a);
int a = 10;
const int *b = &a;
int *c = &a;
print(b);//调用参数中带const的print函数
print(c);//调用参数中不带const的print函数

注意如果print中参数是int a,则会编译报错,不允许重载
const修饰引用函数参数的函数重载

void print(const int &a);
void print(int &a);
int a = 10;
const int &b = a;//引用需要在声明是初始化
int &c = a;//引用需要在声明是初始化
print(b);//调用参数中带const的print函数
print(c);//调用参数中不带const的print函数

const修饰成员函数返回值

指针传递

如果给以“指针传递”方式的函数返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。例如下面的函数

const char * getString();

则char * str = getString();将出现编译错误,必须使用const char* str = getString();

值传递

如果函数返回值采用“值传递方式”,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。

const int getInt();

上面的函数中的const没有任何意义,const int a = getInt();int a = getInt();均可以,但是这里的const没有意义,所以不需要这样使用。同样的const A getA();A是自定义的类型(如类对象),也是没有意义的。

引用传递

如果返回值不是内部数据类型,将函数A GetA(void)改写为const A & GetA(void)的确能提高效率
函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。

例如:

class A {
    A & operate = (const A &other); // 赋值函数
};
A a, b, c; // a, b, c 为A 的对象

a = b = c; // 正常的链式赋值
(a = b) = c; // 不正常的链式赋值,但合法

如果将赋值函数的返回值加const 修饰,那么该返回值的内容不允许被改动。上例中,语句 a = b = c 仍然正确,但是语句 (a = b) = c 则是非法的。

const修饰函数体

1.const修饰函数体时,函数必须是成员函数,如修饰全局函数,编译会报错
2.在声明的时候需要写const,在函数定义的时候也需要写const

class People {
private:
    string name;
public:
    string getName() const;
    string getName();
};

string People::getName() const {//此处必须要写const,否则会报不一致错误。
    return name;
}

3.一个常量成员函数可以被重载为非常量版本。编译器根据这个函数是在哪种类型的对象上调用的,选择需要使用的重载成员函数。

4.常量对象只能调用常函数,非常量对象都可以调用非常函数也可以调用常函数(原因在前面已经解释过,主要跟this指针有关)

const修饰函数参数

参数作输出用

如果参数作输出用,不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const修饰,否则该参数将失去输出功能,因为输出用的参数本身的目的就是在函数中修改,然后返回给调用者。

参数作输入用

const 只修饰输入参数

指针传递

如果输入参数采用“指针传递”,那么加const 修饰可以防止意外地改动该指针,起到保护作用。
例如StringCopy 函数:

void StringCopy(char *strDestination, const char *strSource);

其中strSource是输入参数,strDestination是输出参数。给strSource加上const修饰后,如果函数体内的语句试图改动strSource的内容,编译器将指出错误。

值传递

如果输入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该输入参数本来就无需保护,所以不要加const 修饰。
例如不要将函数void Func1(int x) 写成void Func1(const int x)。同理不要将函数void Func2(A a) 写成void Func2(const A a)。其中A 为用户自定义的数据类型。
对于非内部数据类型的参数而言,像void Func(A a) 这样声明的函数效率比较低。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。

引用传递

为了提高效率,可以将函数声明改为void Func(A &a),因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数void Func(A &a) 存在一个缺点:
“引用传递”有可能改变参数a,这是我们不期望的。只需要加上const修饰即可,因此函数变成void Func(const A &a)
以此类推,是否应将void Func(int x) 改写为void Func(const int &x),以便提高效率?完全没有必要,因为内部数据类型的参数不存在构造、析构的过程,而复制也非常快,“值传递”和“引用传递”的效率几乎相当。
因此
对于非内部数据类型的输入参数,应该将“值传递”的方式改为“const 引用传递”,目的是提高效率。例如将void Func(A a) 改为void Func(const A &a)
对于内部数据类型的输入参数,不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。例如void Func(int x) 不应该改为void Func(const int &x)

参考:
http://www.cnblogs.com/wuqi/p/4573028.html
http://blog.chinaunix.net/uid-12673432-id-2923858.html
http://blog.csdn.net/youoran/article/details/8517611
C++函数重载(2)

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值