现在的位置: 首页 > 综合 > 正文

不喜欢用const的程序员不是好程序员—-小话c++(3)

2013年09月12日 ⁄ 综合 ⁄ 共 4442字 ⁄ 字号 评论关闭

[Mac 10.7.1  Lion  Intel-based  x64  gcc4.2.1  xcode4.2]

Q: c语言中的const和c++中的const关键字的含义是一样的吗?

A: 不尽相同; c语言中const修饰的变量是不可更改的变量,它的核心还是变量;但是c++中const修饰的就是一个常量。比如,在c语言中使用const修饰的一个变量就不可以作为不可变长数组的元素个数,而c++就可以。

Q: const的修饰会影响重载函数吗?

A: 是的。

Q: 为什么下面的代码会提示函数重定义?

#include <iostream>

using namespace std;

int add(int a, int b)
{
    return a + b;
}

int add(const int a, const int b)
{
    return a + b;
}

int main (int argc, const char * argv[])
{
    return 0;
}

Redefinition of 'add'

A: 这是因为const虽然对函数重载有影响,但是仅仅对于形参为引用或者指针类型才有效。因为const int a允许接收非const类型整形作为参数,那么这就和第一个add函数的形式出现了重复,到时候可能编译器也不知道程序员到底想调用哪个函数。

Q: 改成这样的形式就ok了。

int add(int a, int b)
{
    return a + b;
}

int add(const int &a, const int &b)
{
    return a + b;
}

A: 这样确实不会编译出错,但是如果你去使用add函数,很容易就出现编译错误。

#include <iostream>

using namespace std;

#define COUT_ENDL(str)  std::cout << #str << " is " << (str) << std::endl;

int add(int a, int b)
{
    return a + b;
}

int add(const int &a, const int &b)
{
    return a + b;
}

int main (int argc, const char * argv[])
{
    COUT_ENDL(add(1, 2))
    return 0;
}

编译提示:

Call to 'add' is ambiguous

可以看出,编译器根本分不清到底调用哪个add函数。所以,如果需要写类似上面的add代码时,想一想如何去掉一个。

Q: 难道就没有解决方法了吗?

A: 有的,可以将第一个add函数的形式改变一下。改变后如下:

int add(int &a, int &b)
{
    return a + b;
}

int add(const int &a, const int &b)
{
    return a + b;
}

下面测试一下调用情况:

#include <iostream>

using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;
#define COUT_CALL_FUNC(str)  std::cout << "call func: " << __func__ << #str << std::endl;

int add(int &a, int &b)
{
    COUT_CALL_FUNC("add(int &a, int &b)")
    return a + b;
}

int add(const int &a, const int &b)
{
    COUT_CALL_FUNC("add(const int &a, const int &b)")
    return a + b;
}

int main (int argc, const char * argv[])
{
    int i = 10, j = 20;
    COUT_ENDL(add(1, 2))
    COUT_ENDL(add(i, j))
    return 0;
}

输出如下:

add(1, 2) is call func: add"add(const int &a, const int &b)"
3
add(i, j) is call func: add"add(int &a, int &b)"
30

可以看出分别调用了2个版本的add函数。

Q: 既然const能体现出两个函数之间的区别,在类中用const修饰的函数和不用const修饰的函数是否也可以看成重载?

A: 是的。如下代码:

#include <iostream>

using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;
#define COUT_CALL_FUNC(str)  std::cout << "call func: " << __func__ << #str << std::endl;

class Student
{
public:
    Student(int age):_age(age) { }
    ~Student(){ }
    
public:
    int age() { return _age; }
    int age() const { return _age; }
    
private:
    int _age;
};

int main (int argc, const char * argv[])
{
    Student s(25);
    COUT_ENDL(s.age())
    
    return 0;
}

保存为testForCpp.cpp, 编译生成testForCpp, 运行ok.

Q: 如何知道Student类中两个age函数是重载函数?

A: 为了保证符号表中可以看出两个age函数,将代码修改如下:

#include <iostream>

using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;
#define COUT_CALL_FUNC(str)  std::cout << "call func: " << __func__ << #str << std::endl;

class Student
{
public:
    Student(int age):_age(age) { }
    ~Student(){ }
    
public:
    int age() { return _age; }
    int age() const { return _age; }
    
private:
    int _age;
};

int main (int argc, const char * argv[])
{
    Student s(25);
    COUT_ENDL(s.age())
    
    const Student s1(22);
    COUT_ENDL(s1.age())
    
    return 0;
}

重新编译生成testForCpp.

使用nm命令得到可执行文件内部符号表包含age的:

可以看出,确实含有两个和age相关的函数,可以猜测即为代码中的两个age函数。为了确认,调试应用程序,在两个age函数入口加断点,运行:

在第一个age函数处中断,使用disassemble命令得到当前函数的汇编:

(gdb) disassemble
Dump of assembler code for function _ZN7Student3ageEv:
0x0000000100000c70 <_ZN7Student3ageEv+0>:	push   %rbp
0x0000000100000c71 <_ZN7Student3ageEv+1>:	mov    %rsp,%rbp
0x0000000100000c74 <_ZN7Student3ageEv+4>:	mov    %rdi,-0x8(%rbp)
0x0000000100000c78 <_ZN7Student3ageEv+8>:	mov    -0x8(%rbp),%rdi
0x0000000100000c7c <_ZN7Student3ageEv+12>:	mov    (%rdi),%eax
0x0000000100000c7e <_ZN7Student3ageEv+14>:	pop    %rbp
0x0000000100000c7f <_ZN7Student3ageEv+15>:	retq   
End of assembler dump.
(gdb) 

可以看出,函数名确实是__ZN7Student3ageEv(虽然下划线少了一个,因为函数的名称在不同调试器或者可执行文件中可能略有不同).

继续运行,到第二个age函数入口中断:

(gdb) disassemble
Dump of assembler code for function _ZNK7Student3ageEv:
0x0000000100000c80 <_ZNK7Student3ageEv+0>:	push   %rbp
0x0000000100000c81 <_ZNK7Student3ageEv+1>:	mov    %rsp,%rbp
0x0000000100000c84 <_ZNK7Student3ageEv+4>:	mov    %rdi,-0x8(%rbp)
0x0000000100000c88 <_ZNK7Student3ageEv+8>:	mov    -0x8(%rbp),%rdi
0x0000000100000c8c <_ZNK7Student3ageEv+12>:	mov    (%rdi),%eax
0x0000000100000c8e <_ZNK7Student3ageEv+14>:	pop    %rbp
0x0000000100000c8f <_ZNK7Student3ageEv+15>:	retq   
End of assembler dump.
(gdb) 

同样可以确认它的函数名称。

Q: 对于类的拷贝构造函数,为什么形式参数一定得用const修饰,这个很有必要吗?

A: 是的。如下代码:

#include <iostream>

using namespace std;

#define COUT_ENDL(str)      std::cout << #str << " is " << (str) << std::endl;

class Student
{
public:
    Student(int age):_age(age) { }
    ~Student(){ }
    Student(Student &s);
    
public:
    int age() { return _age; }
    int age() const { return _age; }
    
private:
    int _age;
};

Student::Student(Student &s)
{
    _age = s._age;
}

int main (int argc, const char * argv[])
{
    const Student s1(21);
    Student s2(s1);
    COUT_ENDL(s2.age())
    
    
    return 0;
}

Student的拷贝构造函数形式参数并没有const修饰,用const对象s1作为参数传入构造s2便出现了编译错误。

No matching constructor for initialization of 'Student'

原因就在于const对象s1是个不能被修改的对象,使用引用传入可以修改形参的函数,这可能导致意想不到的的情况,所以编译器报错了。

总之,const关键字的加入打乱了原来的语言,语言必须为此付出新的代价,必须在某些时候为const考虑,成就c++体系的完整。

xichen

2012-5-31 16:18:43

抱歉!评论已关闭.