C/C++要点全掌握(三)
上接:C/C++要点全掌握(二)。
上接:C/C++要点全掌握(二)
.11、const辨析
const 是一个“左结合”的修饰符,一般与左侧类型标识符结合声明只读变量(常量);指针修饰符(*)是一个“右结合”修饰符,一般与右侧变量名结合声明指针(在定义指针时可将”*p”看作一个整体,前面类型即为*p内容的数据类型),其优先级高于const与类型修饰符。如果const与类型修饰符(如int)二者直接相连,中间没有指针修饰符(*),那么const和类型修饰符(如int)的前后顺序可以任意。
(一)常见常量的两种定义方法及分析
(1)a为整型常量,a不能为左值(即a不能出现在赋值运算符“=”左边),故a的值不会改变:
int const a=1;
const int a=1;
a=2;//错误
(2) *p为整型常量,*p不能为左值。p为指向i的指针,不能通过*p修改i的值,但直接给i赋值会改变*p的值,可以改变p的值使其不指向i。
int i=1,j;
int const *p=&i;
const int *p=&i;
*p=2;//错误
i=2;//此时*p为2
p=&j;//p指向j
(3)p为整型指针常量,p不能为左值。p为指向i的指针常量,不能修改p的指向,可通过*p修改i的值。
int i=1, j;
int* const p=&i;
const pInt p=&i;//typedef int* pInt;
p=&j;//错误
*p=2;//此时i为2
(4)*p为整型常量,p为整型指针常量,*p和p都不能为左值。不能修改p的指向,也不能通过*p修改i的值。
int i=1, j;
int const* const p=&i;
const int* const p=&i;
p=&j;//错误*p=2;//错误
(5)常量数组。Arr[i]不能为左值。
const int arr[5]={1,2,3,4,5};
int const arr[5]={1,2,3,4,5};
arr[0]=11;//错误
*(arr+1)=22;//错误
(二)const使用注意
(1)const必须初始化
const int a=5;
const int j;//编译警告“使用了未初始化的局部变量”
int k=j;//运行出错
(2) const指针可以接受const和非const的地址,但是非const指针只能接受非const的地址。
const int a=1;
int* p=&a;// 不能用一个非常量指针(p)指向常量(a)
(3)在前面介绍堆栈时已提到给字符指针初始化的字符串是存在于常量区,故不能利用字符指针对其修改。
char *p="tht";
p[0]='2';//编译时没错,运行时访问冲突
(4)在另一连接文件中声明引用const常量时不能赋值。
extern const int i; //合法
extern const int j=10;//非法,常量不可以被再次赋值
(三)const与函数
(1) const修饰形参
void func1(const int *p);//指针(C/C++)
void func2(const int &i);//引用(C++)
void func3(const A &a);//A为自定义类型
如果该参数作传出参数用(相对与高级语言的out关键字,显然该参数不能“按值传递”),不论它是什么数据类型,也不论它采用“指针传递”还是“引用传递”,都不能加const修饰,否则该参数将不能作为左值,进而不能通过它将所需结果传出。
const只能修饰传入参数。
如果传入参数采用“指针传递”(函数func1),那么加const 修饰可以防止意外地改动该指针所指的内容,起到保护作用。如果传入参数采用“引用传递”(函数func2),则const修饰可以保护该参数。如果传入参数采用“值传递”,由于函数将自动产生临时变量用于复制该参数,该参数本来就无需保护,所以不用加const 修饰。
对于非基本数据类型的参数而言,像“void Func(A a)”这样声明的函数注定效率比较底。因为函数体内将产生A 类型的临时对象用于复制参数a,而临时对象的构造、复制、析构过程都将消耗时间。为了提高效率,可以将函数声明改为“void Func(A &a)”,因为“引用传递”仅借用一下参数的别名而已,不需要产生临时对象。但是函数“void Func(A &a)”存在一个缺点:“引用传递”有可能改变实参,这是我们不期望的。解决这个问题很容易,加const修饰即可(函数func3)。
对于内部数据类型的参数,不存在构造、析构的过程,而复制也非常快,故不要将“值传递”的方式改为“const 引用传递”。否则既达不到提高效率的目的,又降低了函数的可理解性。
(2) const修饰返回值
如果给以返回指针方式的函数的返回值加const 修饰,那么函数返回值(即指针)的内容不能被修改,该返回值只能被赋给加const 修饰的同类型指针。
const char* getStr();//声明
char* str=getStr();//错误
const char * str=getStr();//正确
如果函数返回值采用普通值传递方式,由于函数会把返回值复制到外部临时的存储单元中,加const 修饰没有任何价值。
如果返回值不是基本数据类型,将函数“A GetA()”改写为“const A & GetA()”(引用方式)的确能提高效率。但此时千万千万要小心,一定要搞清楚函数究竟是想返回一个对象的“拷贝”还是仅返回“别名”就可以了,否则程序会出错。函数返回值采用“引用传递”的场合并不多,这种方式一般只出现在类的赋值函数中,目的是为了实现链式表达。
class A
{
const A & operate = (const A &other); // 赋值函数
};
A a, b, c; // a, b, c 为A 的对象
c=new A();
a = b = c; //正确(正常的链式赋值)
(a = b) = c; //错误(不正常的链式赋值)
如果将赋值函数的返回值去掉const 修饰,上例中,正常的链式赋值语句“a = b = c”仍然正确,不正常的链式赋值语句“(a = b) = c”也合法。
(3)const修饰成员函数
任何不会修改数据成员的函数都应该声明为const 类型,表明这个函数不会对这个类对象的数据成员(准确地说是非静态数据成员)作任何改变。如果在编写const 成员函数时,不慎修改了数据成员,或者调用了其它非const 成员函数,编译器将指出错误,这无疑会提高程序的健壮性。声明const 成员函数,const关键字只能放在函数声明的尾部。在设计类的时候,一个原则就是对于不改变数据成员的成员函数都要在后面加const。
class A
{
private:
int m_data;
public:
int getData() const;// const成员函数,只读不修改数据成员
void setData(int data);/普通函数 <
补充:软件开发 , C++ ,