您的位置:首页 > 编程语言 > C语言/C++

C++ 基础内容1,c++ 中的const

2006-04-17 22:08 211 查看
C++ 基础内容,不值一提

Author:Jacky Wu 2006-4-17
引用该文章,必须注明其出处 http://blog.csdn.net/imwkj

一:常量 const
1:define 与 const
define用预处理器只能做源代码的文本替代,不能做类型检查,这给程序带来不稳定因素
#define ARRAY_SIZE 100
在预处理过程中,仅仅用值 100 替代在代码中出现的 ARRAY_SIZE,虽然我们在#define 的时候,已经设想它应该是个int型常量,但是,ARRAY_SIZE 并没有任何类型信息。
而用const 可以解决这个问题
const int ARRAY_SIZE = 100;
在此语句中,定义了一个整数型常量,在其作用域内,它是一个恒定不变的常量(这显然很好理解),对于所有内部类型,甚至包括自定义类的对象,都可以使用const 限定符将之表示为一个常量。而这个常量是有类型信息的,在赋值或者参数传递时,会做类型检查,这带来了额外的安全性。
因此,就仅凭这一点,在C++中就应当多用const 而少用 #define 来设置一个常量。

2const 数组
当编译器遇到const 定义时,会根据语句的复杂性来决定是否分配空间给此常量,这有两种情况:
(1) 用extern 来制定该常量为外部连接,或者取const 常量的地址,或者将它作为引用参数传递给函数,这会引起编译器分配内存给该常量
(2) 用const 限定的简单常量,或者在运行期间通过计算获得的常量,编译器将会将它保存在符号表里,不会分配空间给该常量。
对于此特性,并不需要清楚的知道编译器的行为,在程序中,仅需要知道,两点:
(1) 常量在定义的时候是必须赋值的,通过编译时间直接给定或者在编译时间通过计算得到都可以
(2) const 限定的常量在其运行的生命期内是不可以改变的(不可以改变在定义时的初值)
对此,可以通过考察下面的程序来理解这两个特性:
void fun() {
int m = 100; //此处 m 是变量,但是必须得赋初值,否则程序会出现致命错误
const int i = 100; //ok, 用const 限定,不会出现上述问题
const int j = i + 10;
const int n = m + 10; //m 必须是已经赋值的变量

long address = (long)&j; //取得常量J的地址,这将会为j分配空间

const char ch = std::cin.get(); //常量c是通过计算得到,在此后的运行生命期内,是不可以改变的

int a[j]; //OK, j 在编译期间是常量,通过常量表达式计算获得
int b[j+10]; //此为常量表达式

int c
; //?? 这个定义完全可以
}
上面指出了在定义数组时const 显示出的一些特性,但是在用 const限定数组时,又会怎样呢。
在用const限定数组时,仅仅代表“此数组内容是不可以改变的” ,对于数组保存的值,编译期间是无法获得的。看如下代码。
const int arr[] = { 1, 2, 3, 4, 5}
float arr2[ arr[4] ] ; //Illegal, 非法语句,在编译期间,arr[]数组的内容是无法获得的,只能在运行时,通过地址运算获得,而数组在编译时,必须指定数组的大小!
3const 与指针
const 与指针之间的关系包括两方面:const 修饰指针指向的对象(此时表示对象时常量), 和const 修饰指针的地址(此时表示,指针的地址值是常量,该地址不可以作为左值进行运算)。
(1) 指向const对象的指针
定义方式:
const int *ptr;
或者 int const* ptr;
都表示ptr为普通指针,但是ptr所指向的对象是不可以改变的,也就是说,*ptr不可以用作左值操作。
看下面的语句:
const int x = 100;
int m = 200;
const int* ptr; //不需要赋处值

ptr = &m; //这是可以的,这仅仅代表*ptr 不可以改变
//*ptr = 300; //错误,不可以改变*ptr 内容

ptr = & x;
//*ptr = 200; //错误,不可以改变*ptr 内容

(2) 指针的地址是不可以改变的
int m[5] = {1, 2, 3, 4, 5};
int* const ptr = m; //ptr不可以改变,定义时必须赋值,但是*ptr时可以改变的。
*ptr = 200; //运行后,m [0]= 200;
// ptr++; //错误,ptr不可以改变,它仅能表示期定义时的地址。

(3) 把一个const 指针指向一个const 对象。
int m = 100;
const int* const ptr = &m;
//或者 int const* const ptr = &m 两个语句代表一个意思。
显然,*ptr 和ptr 都不可以做左值。
const 限定指针的关系基本就这三个类型,在阅读时你这样理解:在头脑里画一条垂直线穿过指针声明中的星号(*)位置,如果const出现在线的左边,指针指向的数据为常量;如果const出现在线的右边,指针本身为常量;如果const在线的两边都出现,二者都是常量(Effective C++)。
这有几点是要注意的:
(1) 可以将一个非const 对象的地址赋给一个const 指针,例如下面的语句是可以的:
int m = 200;
const int* ptr; //不需要赋处值
ptr = &m; //这是可以的,这仅仅代表*ptr 不可以改变
//*ptr = 300; //错误,不可以改变*ptr 内容
不能把一个const 对象的制止赋给非const 指针,但是可以通过强制类型转换做到这一点(不推荐)
const int m = 200;
//int* ptr = &m; //非法操作,不可以
int* ptr2 = (int*)&m; //合法操作,但是最好不用。
*ptr2 = 300; //合法操作,但完全破坏了常量的安全性检查!!
//运行后,*ptr2 = 300, m=200
// *(int*)&m = 300; 不可理解.这里说明
//(int*)&m &m 是不相等的两个地址!!!

4const 与函数参数,返回值
(1) const 传递值
例如
void fun( const int i)
{
//这里仅代表i 在fun内时不可以变化的
}
为达到这样的效果,最好这样做:
void fun( int ip)
{
const int& i= ip;
//常量引用,异曲同工,效果更好
}
2)返回const
对于内部类型(int, char,等)来说,返回const 值没有太多意义,但是对于用户自定义类型来说,用const限定返回值就保证了函数返回值不能用作左值操作。例如:
class X {
X(int i = 0) {
ii = i;
}
void modify() {
i++;
}

private:
int ii;
};

X Xfun1() {
return X();
}

const X Xfun2() {
return X();
}

void Xfun3(X& rx) {
rx.modify();
}

int main() {
Xfun1() = X(1); //ok,位拷贝给临时变量
Xfun1().modify(); //ok,但是仅对临时变量操作,语句结束后,临时变量被销毁
// Xfun3(Xfun1()); //引用临时对象,可能发生运行期间错误
// Xfun3(Xfun2()); //同上
// Xfun2() = X(1); //错误,试图修改返回的const 对象
// Xfun2().modify(); //同上;
// Xfun3(Xfun2()) //引用传递错误,应当通过常量引用传递,
//但是由于传递临时对象的引用,又可能导致运行时刻错误

}
关于临时对象,可以这样理解:编译时由编译器生成,临时对象的作用域仅仅时该对象产生的表达式,越过此表达式临时对象则被销毁,如果在运行时引用该临时对象,则会发生运行时刻一场。
3const 限定传递或返回地址和引用
void fun1(int* x) {
*x = 100;
//改变了传递过来的原参数值
}

void fun2( const int* x) {
//*x = 100; //错误,*x为常量,不可改变
int i = *x; //OK
const int* ptr = x; //OK
//int *ptr2 = x; //语法错误,见常量与指针说明
}

//对于地址或者引用返回值, 一定要注意,返回对象的地址(引用),对象的生命域一定大于或者等于函数的生命域,不能返回
//生命域在函数内部的对象
//1:可以返回全局变量的地址
//2:可以返回静态变量的地址
//3:可以返回字符串常量的地址
//4:可以返回通过指针或者引用参数传递变量的地址

//5:不可以返回临时对象的地址
//6:不可以返回函数内局部对象的地址
//注意:
//7:除非完全肯定返回的在函数内部通过new建立的堆内存上的对象在随后的操作中一定被delete,
//否则,一定不要返回堆内存上的对象的地址,如果这样做可能会引起内存泄漏.
//这样的函数仅能被自己使用,在类中最好只将之声明为private 函数

const char* fun3() {
return "Hello World!"; //可以返回
}

const char* const fun4() {
static int i;
return &i; //可以返回静态变量,只要了解静态变量的存储区域就很好理解
}

对于参数传递,用const 限定指针或者引用传递,可以阻止在函数中修改原来对象的内容。而通过const 限定的引用传递也可以接收临时对象作为参数。
//const 引用接收临时对象
class X{};

X fun() { return X(); }

void g1(X&) {}
void g2(const X&) {}

int main() {
// g1(fun()); //错误,不能接受临时对象
g2(fun()); //正确,因为临时对象被自动作为常量处理!
}
只要知道临时对象是被编译器处理为常量就可以很好的理解该行为
5const 与类
(1)const 数据成员
对于类中的const 数据成员指的是“该类生成的对象生命期内,该成员是不可变的”,在对象生成之前必须已经赋值。对于const数据成员,唯一可以赋值的地方是“构造函数初始化列表 constructor initializer list),对于const数据成员使用方法如下:
class Array {
public:
Array(int size = 100) : sz(size) {}
int size() { return sz; }
private:
const int sz;
};
显然这里的sz 在类中是不可变更的,但是在创建对象时,可以通过构造函数参数堆sz的之进行初始化,Array(int size = 100) : sz(size) ,其中sz(size) 相当于调用了sz的构造函数,对于内部数据类型是赋初值,对于自定义类类型是调用构造函数。
但是,这样的操作并不能获得和#define 相近的编译期间常量,如果想获得这样的编译期间常量,我们有两中方法:
(a) 使用static const
class Array {
public:
Array() {}
int size() { return sz; }
private:
static const int sz = 100;
int array[sz];
};
下划线部分说明了 sz 是一个编译期间常量
在定义一个static const 常量的时候必须对其初始化,如下:
(b)使用 “enum hack”
使用枚举成员的特点,它在编译期间必须有值,因此,这恰恰获得了编译期间常量的效果,如下:
class Array {
public:
Array() {}
int size() { return sz; }
private:
enum{ sz = 100};
int array[sz];
};
下划线部分说明了 sz 是一个编译期间常量
这是语言本身所具有的一个特性 ,使用它不必担心编译器问题
(2)const 对象和 const 成员函数
对于const对象和const成员函数之间的关系可以这样理解:const 对象只可以调用由const 限定的类的成员函数,而const限定的成员函数不能修改类中的普通的数据成员(通过指针可以修改,但是会出现不必要的麻烦,除非必须,否则不要这样做)。
与const 对象和const成员函数相关的还有两个关键字,mutable(易变的),volatile(不稳定的,可变的)
class X {
public:
X(int m) : i(m) {}
int f() const {
//i++; //错误,不可以在const 成员函数中做改变普通数据成员的操作
return i;
}

int f2() {
i++;
return i;
}
private:
int i;
};

int main() {
X x1(100);
const X x2(200);
x1.f(); //ok,非const对象可以调用const 成员函数
x2.f(); //ok,const 对象只能调用const成员函数
//x2.f1(); //错误const 对象只能调用const成员函数
}
这样,编译器确保了const对象会修改数据成员,从而在逻辑上表示了一个const对象

但是,如果想在const成员函数中修改某些变量该怎么样做呢。有两种方法:
(a)通过对this指针的强制类型转换来获得操作权限(粗体划线代码所示)
(b)通过将变量声明为mutable,表示这是一个在任何时候可以改变的变量,这样允许在const成员函数中改变其值
class X {
public:
X(int m) : i(m) {}
int f() const {
//i++; //错误,不可以在const 成员函数中做改变普通数据成员的操作
((X*)this)->i++; //强制指针类型转换,越过const成员函数检查,正确,但不可取
mi = 100; //正确,mi是mutable变量
return i;
}

int f2() {
i++;
mi++; //正确,mi是mutable变量
return i;
}
private:
int i;
mutable int mi;
};
对于volatile 关键字主要作用是提示编译器该对象的值可能在编译器未监测到的情况下被改变,因此编译器不能武断地对引用这些对象的代码作优化处理。在一般情况下,volatile变量不由用户改变,由系统驻留程序改变。对于此,就不多说明了。

内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: