260 likes | 492 Views
Const. 黄志超 软件 0902. C 中的 const. 问题 1 : const 变量 & 常量 为什么下面的例子在使用一个 const 变量来初始化数组, ANSI C 的编译器会报告一个错误呢? const int n = 5; int a[n];. 变量 n 被修饰为只读变量,可惜再怎么修饰也不是常量。而 ANSI C 规定数组定义时维度必须是“常量” , “只读变量”也是不可以的。 那常量和只读常量有什么区别呢?
E N D
Const 黄志超 软件0902
C中的const 问题1:const变量 & 常量 为什么下面的例子在使用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢? const int n = 5; int a[n];
变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时维度必须是“常量” , “只读变量”也是不可以的。 那常量和只读常量有什么区别呢? 常量肯定是只读的,例如5, “abc”,等,肯定是只读的,因为常量是被编译器放在内存中的只读区域,当然也就不能够去修改它。而“只读变量”则是在内存中开辟一个地方来存放它的值,只不过这个值由编译器限定不允许被修改。 但在标准C++中,这样定义的是一个常量,这种写法是对的。
问题2:const变量 & const 限定的内容 下面的代码编译器会报一个错误,请问,哪一个语句是错误的呢? typedef char * pStr; char string[4] = "abc"; const char *p1 = string; const pStr p2 = string; p1++; p2++; 问题出在p2++上
分析: typedef和#define不同,它不是简单的文本替换。上述代码中const pStr p2并不等于const char * p2。const pStr p2和const long x本质上没有区别,都是对变量进行只读限制,只不过此处变量p2的数据类型是我们自己定义的而不是系统固有类型而已。因此,const pStr p2的含义是:限定数据类型为char *的变量p2为只读,因此p2++错误。 • 由于p2不是指针,const 直接修饰到了p2,即现在的p2是常量了,它的类型是pStr(我们自己定义的类型),相当于const int p2, const long p2等等,const都是直接修饰p2的,只不过int,long是系统类型,而pStr是我们定义的类型。为什么会出现这种效果了,就是因为 typedef,它把char *定义成一个复合的类型,要从整体上来理解语义,而不是字符替换后来理解语义。
问题3:const变量 & 字符串常量 请问下面的代码有什么问题? char *p = "i'm hungry!"; p[0]= 'I'; 分析: “i'm hungry”实质上是字符串常量,而常量往往被编译器放在只读的内存区,不可写。
问题4:const & 指针 下面分别用const限定不可变的内容是什么? const int n;或者 int const n; const char *p; 或者 char const * p; char* const p; const char* const p; 或者 char const* const p; 答案: n是const *p是const, p可变 p是const,*p可变 p和*p都是const
C++中的const 一、关于一般常量 const int bufSize = 512; 因为常量在定义后就不能被修改,所以定义时必须初始化。 二、关于数组及结构体 const int cntIntArr[] = {1,2,3,4,5}; struct SI { int i1; int i2; }; const struct SI s[] = {{1,2},{3,4}};
三、关于引用 const int i = 128; const int &r = i; //引用要在定义时初始化 普通引用不能绑定到const 对象,但const 引用可以绑定到非const 对象。 const int ii = 456; int &rii = ii; // error int jj = 123; const int &rjj = jj; // ok
非const 引用只能绑定到与该引用同类型的对象。 const 引用则可以绑定到不同但相关的类型的对象. 例如: double dVal = 3.1415; int &ri = dVal; //错误 const int &ri = dVal; //警告
四、关于指针 问题:以下两个有什么区别? • int i = 100; const int *p = &i; • int i = 100; int *const p = &i;
如何将一个const 对象合法地赋给一个普通指针??? 例如: • const double dVal = 3.14; double *ptr = &dVal; // error double *ptr = const_cast<double*>(&dVal); // ok: const_cast是C++中标准的强制转换, 在C语言中警告,使用: double *ptr = (double*)&dVal;
个人问题: 在C语言中: • const double d = 3.14; double *ptr =(double *) &d; *ptr = 5; printf( “*ptr = %f \n”, *ptr ); //*ptr = 5.000000 printf( “d = %f \n”, d ); //d = 5.000000 printf( “ptr = %p \n”, ptr ); //ptr = 0xbffcc92c printf( "&d = %p \n", &d ); //&d=0xbffcc92c 当把const double d = 3.14;作为全局变量时,出现段错误
在C++中: • const double d = 3.14; double *ptr = const_cast<double>(&d); *ptr = 5; printf( “*ptr = %f \n”, *ptr ); //*ptr = 5.000000 printf( “d = %f \n”, d ); //d = 3.140000 printf( “ptr = %p \n”, ptr ); //ptr = 0xbff94890 printf( "&d = %p \n", &d ); //d = 0xbff94890 • 当把const double d = 3.14;作为全局变量的时候出现段错误
2.const 指针(指针本身为常量) int i = 100; int *const p = &i; 指针的指向不能被修改。 *p = 1; //指针所指向的基础对象可以修改。 3.指向const 对象的const 指针(指针本身和指向的内容均为常量) const double pi = 3.14159; const double *const pi_ptr = π 指针的指向不能被修改,指针所指向的基础对象也不能被修改。
假如涉及的是一级间接关系,则将非const指针赋给const指针是可以的。假如涉及的是一级间接关系,则将非const指针赋给const指针是可以的。 例如: int age = 39; int *pd = &age; const int *pt = pd; 不过进入两级间接关系时,与一级间接关系一样将const和非const混合的指针赋值方式将不再安全。 请看下面的代码,有什么问题吗? const int **pp2; int *p1; pp2 = &p1; //错误 分析: const int **pp2;整条语句应该这样翻译:pp2是一个指向指针对象的指针,而这个指针对象指向的又是一个int型的常量(const)。 把 int *p1; 改为const int *p1;则就可以通过编译。
五、关于一般函数 1.修饰函数的参数 class A; void f (const A &rA); // rA所引用的对象不能被修改 2.修饰函数的返回值 返回值:const int func1(); 返回引用:const int &func2(); // 注意千万不要返回局部对象的引用,否则会报运行时错误:因为一旦函数结束,局部对象被释放,函数返回值指向了一个对程序来说不再有效的内存空间。 返回指针:const int *func3(); // 注意千万不要返回指向局部对象的指针,因为一旦函数结束,局部对象被释放,返回的指针变成了指向一个不再存在的对象的悬垂指针。
六、关于类 class A { public: void func(); void func() const; const A operator+(const A &) const; private: int num1; mutable int num2; const size_t size; }; 1.修饰成员变量 const size_t size; // 对于const的成员变量,[1]必须在构造函数里面进行初始化;[2]只能通过初始化成员列表来初始化;[3]试图在构造函数体内对const成员变量进行初始化会引起编译错误。 例如: A::A( size_t sz ) : size( sz ) { }// ok:使用初始化成员列表来初始化 A::A( size_t sz ) { size = sz } // error
2.修饰类成员函数 void func() const; // const成员函数中不允许对数据成员进行修改.如果某成员函数不需要对数据成员进行修改,最好将其声明为const 成员函数,这将大大提高程序的健壮性。 const 为函数重载提供了一个参考 class A { public: void func(); // [1]:一个函数 void func() const; // [2]:上一个函数[1]的重载 …… }; A a(10); a.func(); // 调用函数[1] const A b(100); b.func(); // 调用函数[2]
为了确保const对象的数据成员不会被改变,在C++中,const对象只能调用const成员函数。如果一个成员函数实际上没有对数据成员作任何形式的修改,但是它没有被const关键字限定的,也不能被常量对象调用。下面通过一个例子来说明这个问题:为了确保const对象的数据成员不会被改变,在C++中,const对象只能调用const成员函数。如果一个成员函数实际上没有对数据成员作任何形式的修改,但是它没有被const关键字限定的,也不能被常量对象调用。下面通过一个例子来说明这个问题: class C { int X; public: int GetX() { return X; } void SetX(int X) { this->X = X; } }; void main() { const C constC; cout<<constC.GetX(); } 如果我们编译上面的程序代码,编译器会出现错误提示:constC是个常量对象,它只能调用const成员函数。虽然GetX( )函数实际上并没有改变数据成员X,由于没有const关键字限定,所以仍旧不能被constC对象调用。 将int GetX()改为int GetX() const 即可。
非常量成员函数不能被常量成员对象调用,因为它可能企图修改常量的数据成员:非常量成员函数不能被常量成员对象调用,因为它可能企图修改常量的数据成员: • public: • Set (void){ card = 0; } • bool Member(const int) const; • void AddElem(const int); • //... • }; • Set s; • s.AddElem(10); // 非法: AddElem不是常量成员函数 • s.Member(10); // 正确
3.修饰类对象 const A a; // 类对象a 只能调用const 成员函数,否则编译器报错。 4.修饰类成员函数的返回值 const A operator+(const A &) const; // 前一个const 用来修饰重载函数operator+的返回值,可防止返回值作为左值进行赋值操作。 例如: A a; A b; A c; a + b = c; // errro: 如果在没有const 修饰返回值的情况下,编译器不会报错。
Const 与define的异同 • const与define两者都可以用来定义变量,但是const定义时,定义了常量的类型,而#define只是简单的文本替换。 • Const与define的最大的差别在于:前者在堆栈分配了空间,而后者只是把具体数值直接传递到目标变量。或者说const的常量是一个Run-Time的概念,它在程序中确确实实的存在并可以被调用、传递。而#define常量则是一个Compile-Time概念,它的生命周期止于编译期;在实际程序中他只是一个常数、一个命令中的参数,没有实际的存在。 • const常量存在于程序的数据段,#define常量存在于程序的代码段。const常量有数据类型,宏常量没有数据类型,编译器可以对const常量进行类型的安全检查。
七、const有什么主要的作用? 1.可以定义const常量,具有不可变性。 例如: const int Max=100; int Array[Max]; 2.便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。 例如: void f(const int i) { .........} 编译器就会知道i是一个常量,不允许修改; 3.可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。 同宏定义一样,可以做到不变则已,一变都变! 4.可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。
5.提高了效率。 编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。