const与#define区别:
1.const常量有数据类型,宏常量没有数据类型。宏常量直接替换不进行类型检查,这样就可能会产生不可预料的错误
2.可以对const常量调试,但不能对宏常量调试。
3.宏常量只在C中使用,C++中只使用const 常量不使用宏常量。
因为常量在定义后就不能被修改,所以定义时必须初始化: |
const std::string hi = "hello!"; //ok: initialized
const int i, j = 0; //error:i is uninitialized const
指向const对象的指针
如果指针指向 const 对象,则不允许用指针来改变其所指的 const 值。为了保证这个特性,C++ 语言强制要求指向 const 对象的指针也必须具有 const 特性:
const double *cptr; //cptr may point to a double that is const
这里的 cptr 是一个指向 double 类型const 对象的指针,const 限定了 cptr 指针所指向的对象类型,而并非 cptr 本身。也就是说,cptr 本身并不是 const。在定义时不需要对它进行初始化,如果需要的话,允许给 cptr 重新赋值,使其指向另一个 const 对象。但不能通过 cptr 修改其所指对象的值:
*cptr = 42; //error:*cptr might be const
把一个 const 对象的地址赋给一个普通的、非 const 对象的指针也会导致编译时的错误:
const double pi = 3.14;
double *ptr =pi; //error: ptr is a plain pointer
const double *cptr =pi; //ok: cptr is a pointer to const
不能使用 void* 指针保存 const 对象的地址,而必须使用 const void* 类型的指针保存 const 对象的地址:
const int universe = 42; const void *cpv = &universe; // ok: cpv is const void *pv = &universe; // error: universe is const
允许把非 const 对象的地址赋给指向 const 对象的指针,例如:
double dval = 3.14; // dval is a double; its value can be changed cptr = &dval; // ok: but can't change dval through cptr
综上有:const对象的地址一定要赋值给指向const对象的指针。
指向const对象的指针既可以指向const对象也可以指向非const对象。
尽管 dval 不是 const 对象,但任何企图通过指针 cptr 修改其值的行为都会导致编译时的错误。
cptr 一经定义,就不允许通过该指针修改其所指对象的值。即使该指针指向非 const 对象,同样必须遵循这个规则。
dval = 3.14159; //dval is not const
*cptr = 3.14159; //error:cptr is a pointer to const
但是:
如果指向 const 的指针所指的对象并非 const,则可直接给该对象赋值或间接地利用普通的非 const 指针修改其值:毕竟这个值不是const。
注:这里的前提是,const 指针cptr已经指向了非const对象dval,通过下面的代码修改dval的值
double *ptr = &dval; //ok:ptr points at non-const
double *ptr = 2.72; //ok:ptr is plain pointer
cout << *cptr; //ok: prints 2.72
重要的是要记住:不能保证指向 const 的指针所指对象的值一定不可修改。
在实际的程序中,指向 const 的指针常用作函数的形参。将形参定义为指向 const 的指针,以此确保传递给函数的实际对象在函数中不因为形参而被修改。
一句话总结:在定义时指向const对象的指针(const int *cptr)可以将其指向非const对象,非const对象的值可以被修改,但不能通过指针cptr来修改。
const指针
除指向 const 对象的指针外,C++ 语言还提供了 const 指针——本身的值不能修改:
int errNumb = 0; int *const curErr = &errNumb; // curErr is a constant pointer
我们可以从右向左把上述定义语句读作“curErr 是指向 int 型对象的 const 指针”。与其他 const 量一样,const 指针的值不能修改,这就意味着不能使 curErr 指向其他对象。任何企图给 const 指针赋值的行为(即使给 curErr 赋回同样的值)都会导致编译时的错误:
curErr = curErr; // error: curErr is const
与任何const 量一样,const 指针也必须在定义时初始化。
指针本身是 const 的事实并没有说明是否能使用该指针修改它所指向对象的值。指针所指对象的值能否修改完全取决于该对象的类型。
指向const对象的const指针
还可以如下定义指向 const 对象的 const 指针:
const double pi = 3.14159; // pi_ptr is const and points to a const object const double *const pi_ptr = π
本例中,既不能修改 pi_ptr 所指向对象的值,也不允许修改该指针的指向(即 pi_ptr 中存放的地址值)。可从右向左阅读上述声明语句:“pi_ptr 首先是一个 const 指针,指向 double 类型的 const 对象”。
指针和 typedef
string s; typedef string *pstring; const pstring cstr1 = &s; // written this way the type is obscured pstring const cstr2 = &s; // all three decreations are the same type string *const cstr3 = &s; // they're all const pointers to string
形参与实参
每次调用函数时,都会重新创建该函数所有的形参,此时所传递的实参将会初始化对应的形参。
引用形参和非引用形参(即传址与传值)
非引用形参的初始化使用实参副本,因此不会修改实参的值
引用形参是实参的别名,会修改实参的值
const与形参
当非引用形参是指针类型且被const限定时,可以接受什么样的实参,遵照前面的规则。
当非引用形参是普通类型(如int,double等)时,实参既可以是const类型也可以是非const类型。遵循const对象可以被读取但不可被修改的规则。
如果将形参定义为非引用的 const 类型:
void fcn(const int i) { /* fcn can read but not write to i */ }
由于实参仍然是以副本的形式传递,因此传递给 fcn 的既可以是 const 对象也可以是非 const 对象。
当非引用形参是普通类型且被const限定时,实参既可以是const类型也可以是非const类型,但在函数中不可以改变实参的副本。
复制实参的局限性
复制实参并不是在所有的情况下都适合,不适宜复制实参的情况包括:
当需要在函数中修改实参的值时。
当需要以大型对象作为实参传递时。对实际的应用而言,复制对象所付出的时间和存储空间代价往往过在。
当没有办法实现对象的复制时。
对于上述几种情况,有效的解决办法是将形参定义为引用或指针类型。(注意,引用与指针的区别)
const引用(指向const对象的引用)
引用形参:从 C 语言背景转到 C++ 的程序员习惯通过传递指针来实现对实参的访问。在 C++ 中,使用引用形参则更安全和更自然。
1.函数只能返回单个值,但有些时候,函数有不止一个的内容需要返回。例如,定义一个 find_val 函数。在一个整型 vector 对象的元素中搜索某个特定值。如果找到满足要求的元素,则返回指向该元素的迭代器;否则返回一个迭代器,指向该 vector 对象的 end 操作返回的元素。此外,如果该值出现了不止一次,我们还希望函数可以返回其出现的次数。在这种情况下,返回的迭代器应该指向具有要寻找的值的第一个元素。
如何定义既返回一个迭代器又返回出现次数的函数?1.我们可以定义一种包含一个迭代器和一个计数器的新类型。2.而更简便的解决方案是给find_val 传递一个额外的引用实参,用于返回出现次数的统计结果:
// returns an iterator that refers to the first occurrence of value // the reference parameter occurs contains a second return value vector<int>::const_iterator find_val( vector<int>::const_iterator beg, // first element vector<int>::const_iterator end, // one past last element int value, // the value we want vector<int>::size_type &occurs) // number of times it occurs { // res_iter will hold first occurrence, if any vector<int>::const_iterator res_iter = end; occurs = 0; // set occurrence count parameter for ( ; beg != end; ++beg) if (*beg == value) { // remember first occurrence of value if (res_iter == end) res_iter = beg; ++occurs; // increment occurrence count } return res_iter; // count returned implicitly in occurs }
调用 find_val 时,需传递四个实参:一对标志 vector 对象中要搜索的元素范围的迭代器,所查找的值,以及用于存储出现次数的size_type 类型对象。假设 ivec 是 vector<int>, it 类型的对象,it 是一个适当类型的迭代器,而 ctr 则是 size_type 类型的变量,则可如此调用该函数:
it = find_val(ivec.begin(), ivec.end(), 42, ctr);
调用后,ctr 的值将是 42 出现的次数,如果 42 在 ivec 中出现了,则 it 将指向其第一次出现的位置;否则,it 的值为 ivec.end(),而ctr 则为 0。
2.利用const引用避免复制
在向函数传递大型对象时,需要使用引用形参,这是引用形参适用的另一种情况。虽然复制实参对于内置数据类型的对象或者规模较小的类类型对象来说没有什么问题,但是对于大部分的类类型或者大型数组,它的效率(通常)太低了;此外,我们将在第十三章学习到,某些类类型是无法复制的。使用引用形参,函数可以直接访问实参对象,而无须复制它。
编写一个比较两个 string 对象长度的函数作为例子。这个函数需要访问每个 string 对象的 size,但不必修改这些对象。由于 string 对象可能相当长,所以我们希望避免复制操作。使用 const 引用就可避免复制:
// compare the length of two strings bool isShorter(const string &s1, const string &s2) { return s1.size() < s2.size(); }
其每一个形参都是 const string 类型的引用。因为形参是引用,所以不复制实参。又因为形参是 const 引用,所以 isShorter 函数不能使用该引用来修改实参。
如果使用引用形参的唯一目的是避免复制实参,则应将形参定义为 const 引用。
3.指向 const 的引用更加灵活
非 const 引用形参(第 2.5 节)只能与完全同类型的非 const 对象关联。
// function takes a non-const reference parameter int incr(int &val) { return ++val; } int main() { short v1 = 0; const int v2 = 42; int v3 = incr(v1); // error: v1 is not an int v3 = incr(v2); // error: v2 is const v3 = incr(0); // error: literals are not lvalues v3 = incr(v1 + v2); // error: addition doesn't yield an lvalue int v4 = incr(v3); // ok: v3 is a non const object type int }
尽量将引用形参定义为const,除非需要修改该形参。普通的非 const 引用形参在使用时不太灵活。这样的形参既不能用 const 对象初始化,也不能用字面值或产生右值的表达式实参初始化。
指向指针的引用
假设我们想编写一个与前面交换两个整数的swap类似的函数,实现两个指针的交换。已知需用*定义指针,用&定义引用。现在,问题在于如何将这两个操作符结合起来以获得指向指针的引用。这里给出一个例子:
// swap values of two pointers to int void ptrswap(int *&v1, int *&v2) { int *tmp = v2; v2 = v1; v1 = tmp; }
形参int *&v1可看作int*与&v1结合而来
定义应从右至左理解:v1是一个引用,与指向int型对象的指针相关联。也就是说,v1只是传递进ptrswap函数的任意指针的别名。重写第 7.2.2 节的main函数,调用ptrswap交换分别指向值 10 和 20 的指针:
int main() { int i = 10; int j = 20; int *pi = &i; //pi points to iint *pj = &j; //pj points to jcout << "Before ptrswap():\t*pi: " << *pi << "\t*pj: " << *pj << endl; ptrswap(pi, pj); //now pi points to j; pj points to icout << "After ptrswap():\t*pi: " << *pi << "\t*pj: " << *pj << endl; return 0; }
编译并执行后,该程序产生如下结果:
Before ptrswap(): *pi: 10 *pj: 20 After ptrswap(): *pi: 20 *pj: 10
即指针的值被交换了。在调用ptrswap时,pi指向i,而pj则指向j。在ptrswap函数中,指针被交换,使得调用ptrswap结束后,pi指向了原来pj所指向的对象。换句话说,现在pi指向j,而pj则指向了i。
常量成员函数
每个成员函数(除了在第 12.6 节介绍的 static 成员函数外)都有一个额外的、隐含的形参 this。在调用成员函数时,形参 this 初始化为调用函数的对象的地址。class Sales_item { public: // operations on Sales_item objects double avg_price() const; bool same_isbn(const Sales_item &rhs) const { return isbn == rhs.isbn; } // private members as before private: std::string isbn; unsigned units_sold; double revenue; };
为了理解成员函数的调用,可考虑下面的语句:
total.same_isbn(trans);
就如编译器这样重写这个函数调用:
// pseudo-code illustration of how a call to a member function is translated Sales_item::same_isbn(&total, trans);
在这个调用中,函数 same_isbn 中的数据成员 isbn 属于对象 total。
理解const:const 改变了隐含的 this 形参的类型。在调用 total.same_isbn(trans) 时,隐含的 this 形参将是一个指向 total 对象的const Sales_Item* 类型的指针。就像如下编写 same_isbn 的函数体一样:
// pseudo-code illustration of how the implicit this pointer is used // This code is illegal: We may not explicitly define the this pointer ourselves // Note that this is a pointer to const because same_isbn is a const member bool Sales_item::same_isbn(const Sales_item *const this, const Sales_item &rhs) const { return (this->isbn == rhs.isbn); }
用这种方式使用 const 的函数称为常量成员函数。由于 this 是指向 const 对象的指针,const 成员函数不能修改调用该函数的对象。因此,函数 avg_price 和函数 same_isbn 只能读取而不能修改调用它们的对象的数据成员。
const 对象、指向 const 对象的指针或引用只能用于调用其 const 成员函数,如果尝试用它们来调用非 const 成员函数,则是错误的。