【C++】const详解

本文详细探讨了C++中的const用法,包括const常量、const修饰函数、const修饰类以及const类型转换为非const类型的方法。强调const在提供程序安全性方面的价值,指出const常量并非真正的不可变,并介绍了const与C语言中#define的区别。此外,还讲解了const在指针、函数参数、成员函数和类对象中的应用。

常量概念是为了使程序员能够在变和不变之间画一条界限。给C++程序设计项目中提供了安全性和可控性。——《C++编程思想》

在编程中要尽可能多的使用const,这样可以获得编译器的帮助,以便写出健壮性的代码。 

define宏定义,可以将常量赋值给一个变量,但由于在编译过程中只是简单的进行文本替换,并不会对赋值语句进行检查,因此会存在很多潜在的威胁,一旦定义出错,整个调用过程也讲出错。所以C++创始人为了避免这种错误才提出了用const来代替,同时会对所定义的常量进行语法检查。 并且defne是不会给名字分配内存空间的,仅仅是一个常数的别名而已(字面常量存储在常量存储区或立即数形式出现),但是const变量会分配自己的内存空间,具有自己的地址。从这以后可以被用于指针、函数变量、返回类型、类对象和成员函数,用法有所区别,但是概念是一致的。

#define PI 3.1415926 //一般出现在头文件中,不是严格的语句不加“;”

const length = 10;//若不加类型名就默认是int类型,等同于 const int length = 10;

一、const常量

使用形式: const 类型 变量名 = 常量值;

定义常量时需要同时进行初始化!例如:const int a = 10; const int arr[3] = {0,1,2};只有一种情况是不需要初始化的,那就是声明为外部变量。

const定义的变量并不是存储在常量存储区域。const类型的存储跟一般的变量没有区别,在外部定义的存储在全局数据区,static的存储在静态数据区,在函数内部定义的存储在栈,const跟非const存储上没区别。

事实上,const在C和C++中是不同的!

//C
int main()
{
	const int n = 5;
	int arr[n];
	return 0;
}
---------------------------
output:
1>------ 已启动全部重新生成: 项目: Project1, 配置: Debug Win32 ------
1>  main.c
1>e:\cpp\cppprimer\project1\main.c(5): error C2057: 应输入常量表达式
1>e:\cpp\cppprimer\project1\main.c(5): error C2466: 不能分配常量大小为 0 的数组
1>e:\cpp\cppprimer\project1\main.c(5): error C2133: “arr”: 未知的大小
========== 全部重新生成: 成功 0 个,失败 1 个,跳过 0 个 ==========

在C语言中用常量n去初始化数组arr会发现出现错误。

主要原因是:

1.这个问题讨论的是“常量”与“只读变量”的区别。常量肯定是只读的,例如数字5, 字符串“abc”等。而“只读变量”则是在内存中开辟一个地方来存放它的值,只不 过这个值由编译器限定不允许被修改。C语言关键字const就是用来限定一个变量不允许被改变的修饰符。上述代码中变量n被修饰为只读变量,可惜再怎么修饰也不是常量。而ANSI C规定数组定义时维度必须是“常量”,“只读变量”也是不可以的。

2.在ANSI C中,这种写法是错误的,因为数组的大小应该是个常量,而const int n,n只是一个变量(常量 != 不可变的变量,但在标准C++中,这样定义的是一个常量,这种写法是对的),实际上,根据编译过程及内存分配来看,这种用法本来就应该是合理的,只是 ANSI C对数组的规定限制了它。那么在c语言中我们如果想定义一个事实常量该怎么定义呢?在C语言中我们定义常量的方法可以通过#define宏定义或者enum,这两个是C语言中常用的定义常量的方法。这样上述问题就可以解决了。

用这样的方法在C++中定义是没有问题的。

//C++
int main()
{
	const int n = 5;
	int arr[n];
	return 0;
}
1>------ 已启动全部重新生成: 项目: 测试, 配置: Debug x64 ------
1>  main.cpp
1>e:\cpp\测试\测试\main.cpp(20): warning C4101: “arr”: 未引用的局部变量
1>  测试.vcxproj -> E:\CPP\测试\x64\Debug\测试.exe
1>  测试.vcxproj -> E:\CPP\测试\x64\Debug\测试.pdb (Full PDB)
========== 全部重新生成: 成功 1 个,失败 0 个,跳过 0 个 ==========

简单地说,C++中可以使用const声明常量,C中在定义数组维数时还是会把const常量认为是不那么“常量”的常量。

虽说const修饰的变量视为不可修改的常量,但是实际上指的是不可直接修改,可以用非常规方法修改数值的。

#include <stdio.h>
int main()
{
	const int  a = 7;
	printf("const int a = %d \n", a);
	int  *p = /*(int*)*/&a;
	*p = 8;
	printf("const int a = %d \n", a);
	return 0;
}
输出: 
const int a = 7
const int a = 7

 对于const变量a,初始化常量a=7,但是通过重新对a地址内的值赋值可以改变a。从监视上看

a已经被修改为8了但是编译器还是认为是一开始初始化的7。这时候可以利用volatile关键字来解决。

另外需要注意:C++的const是升级版!

C++不允许:int  *p = /*(int*)*/&a;这样的方式,c++对类型检查更为严格,int  *p = (int*)&a;就可以了。

----------------------------------------

#include<iostream>
using namespace std;
int main()
{
	int a1 = 3;			//变量
	const int a2 = a1;	//常量 等效于 int const a2 = a1;

	int * a3 = &a1;					//non-const data,non-const pointer
	const int * a4 = &a1;   //const data,non-const pointer
	int * const a5 = &a1;   //non-const data,const pointer
	int const * const a6 = &a1;   //const data,const pointer
	const int * const a7 = &a1;   //const data,const pointer

	// int * a8 = &a2;
	const int * a9 = &a2; 
	// int * const a10 = &a2; 
	int const * const a11 = &a2;  
	const int * const a12 = &a2; 

	return 0;
}

 1、定义常量
(1)const修饰变量,以下两种定义形式在本质上是一样的。它的含义是:const修饰的类型为TYPE的变量value是不可变的。

TYPE const ValueName = value;   const TYPE ValueName = value; 

(2)将const改为外部连接,作用于扩大至全局,编译时会分配内存,并且可以不进行初始化,仅仅作为声明,编译器认为在程序其他地方进行了定义.

extern const int ValueName = value;

2、指针使用const

(1)指针本身是常量不可变
     char* const pContent; 

(2)指针所指向的内容是常量不可变
     const char *pContent;  char const *pContent; 

(3)两者都不可变
      const char* const pContent; 

还有其中区别方法,以*为界限,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。

二、const修饰函数

使用形式:const 返回值类型 函数(const 参数)

1、const修饰函数参数

A:值传递的const修饰传递,一般这种情况不需要const修饰,因为函数会自动产生临时变量复制实参值。

#include<iostream>
using namespace std;

void Function(const int a)

{
    cout<<a;
    // ++a; //不可修改!
}

int main(void)

{
    Function(8);
    system("pause");
    return 0;
}

这里的const是做了一个约定,变量的初值是不可以被函数改变的,但是由于参数是值传递的,会立即产生变量副本,所以这个对外是隐式的。

B:当const参数为指针时,可以防止指针被意外篡改。

#include<iostream>
using namespace std;

void Function(int *const a)
{
    cout<<*a<<" ";
    *a = 9;
}

int main(void)

{
    int a = 8;
    Function(&a);
    cout<<a; // a is 9
    return 0;
}

C:自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取const外加引用传递的方法。并且对于一般的int ,double等内置类型,我们不采用引用的传递方式。

#include<iostream>
using namespace std;

class Test

{
public:
    Test(){}
    Test(int _m):_cm(_m){}
    int get_cm() const
    {
       return _cm;
    }
private:
    int _cm;
};

void Function(const Test& _tt)
{
    cout<<_tt.get_cm();
}

int main(void)
{
    Test t(8);
    Function(t);
    return 0;
}
结果输出 8

2、const修饰函数返回值

const修饰返回值分三种情况。

A:const修饰内置类型的返回值,修饰与不修饰返回值作用一样。

#include<iostream>
using namespace std;

const int Function1()
{
    return 1;
}

int Function2()
{
    return 0;
}

int main(void)
{
    int _m = Function1();
    int _n = Function2();
    cout<<_m<<" "<<_n;
    return 0;
}

对于返回值来说,如果是常量,就约定了函数框架里的原变量不会被修改,另外因为是按值返回的,所这个变量会制成副本,初值不会被返回值所修改。这就显得const在这里没有什么作用了。对于内部类型来说,按值返回内部类型时候去掉const会更好。

B:const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。

如果想限制这个对象作为左值使用,就可以使用const。

class X 
{
    int i;
public:
    X(int ii = 0);
    void modify();
};

X::X(int ii) { i = ii; }

void X::modify() { i++; }

X f5() {
  return X();
}

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

void f7(X& x) { // Pass by non-const reference
  x.modify();
}

int main() 
{
  f5() = X(1); // OK -- non-const return value
  f5().modify(); // OK
// Causes compile-time errors:
//!  f7(f5());
//!  f6() = X(1);
//!  f6().modify();
//!  f7(f6());
return 0;
} 

C: const 修饰返回的指针或者引用,是否返回一个指向const的指针,取决于我们想让用户干什么。

如果传递或者返回一个地址(一个指针或引用),那么修改其初值是可能的!但是若返回为const,就会限制这样的事情发生。是否返回一个指向const的指针或者引用,取决于我们想让用户干什么。

// Constant pointer arg/return

void t(int*) {}

void u(const int* cip) 
{
//!  *cip = 2; // Illegal -- modifies value
  int i = *cip; // OK -- copies value
//!  int* ip2 = cip; // Illegal: non-const
}

const char* v() 
{
  // Returns address of static character array:
  return "result of function v()";
}

const int* const w() 
{
  static int i;
  return &i;
}

int main() 
{
  int x = 0;
  int* ip = &x;
  const int* cip = &x;
  t(ip);  // OK
//!  t(cip); // Not OK
  u(ip);  // OK
  u(cip); // Also OK

//!  char* cp = v(); // Not OK
  const char* ccp = v(); // OK
//!  int* ip2 = w(); // Not OK
  const int* const ccip = w(); // OK
  const int* cip2 = w(); // OK
//!  *w() = 1; // Not OK
    return 0;
} 

可以从main中可以看出t()和u()的使用情况。

函数t接非const指针,但是如果传入const指针参数就不能防止函数t修改指针内容,所以编译出错。函数u参数接受const指针,所以接受两种类型的参数。

函数v的返回值只可以被赋值给一个const指针,编译器拒绝w的返回值赋值给一个非const指针,接受const int * const,但是同时它也接受const int*,这不是与返回类型恰好匹配的。因为这个值(包含在指针中的地址)正被拷贝,所以自动保持这样的约定:原始变量不能被改变。因此const int* const中的第二个const当做一个左值使用时(编译器会阻止这样的情况)。

也就是说:函数原型 const int * w() 和 const int * const w()是一样的。由于c++中严格检查类型匹配问题,所以我们要知道,在const修饰的指针变量中,顶层const在有赋值效果的操作中将被忽略。

三、const修饰类

1、const修饰成员变量

const修饰类的成员函数,表示成员常量,不能被修改,同时它只能在初始化列表中赋值。

const成员函数能够访问对象的const成员,而其他成员函数不可以,也就是说const成员变量不能在非const成员函数中使用。(const成员函数可以访问成员变量不论是否为const)

#include <iostream>
using namespace std;

class Fred 
{
  const int size;
public:
  Fred(int sz);
  void print();
};

Fred::Fred(int sz) : size(sz) {}
void Fred::print() { cout << size << endl; }

int main() 
{
  Fred a(1), b(2), c(3);
  a.print(), b.print(), c.print();
}

把一个const放在类中,当每个对象需要有自己独有的常量时将会在构造函数中以参数列表的方式初始化常量。

2、const修饰成员函数

const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为const成员函数。注意:const关键字不能与static关键字同时使用,因为static关键字修饰静态成员函数,静态成员函数不含有this指针,即不能实例化,const成员函数必须具体到某一实例。 构造、析构函数不允许使用 const 类型限制符!

#include<iostream>
using namespace std;

class Test
{
public:
    Test(){}
    Test(int _m):_cm(_m){}
    int get_cm() const
    {
        //++_cm;
        _ct++;
       return _cm;
    }
private:
    int _cm;
    mutable int _ct = 0;
};

 
void Cmf(const Test& _tt)
{
    cout<<_tt.get_cm();
}

int main(void)
{
    Test t(8);
    Cmf(t);
    return 0;
}

如果get_cm()去掉const修饰,则Cmf传递的const _tt即使没有改变对象的值,编译器也认为函数会改变对象的值,所以我们尽量按照要求将所有的不需要改变对象内容的函数都作为const成员函数。

但是,如果有个成员函数想修改对象中的某一个成员怎么办?这时我们可以使用mutable关键字修饰这个成员,mutable的意思也是易变的,容易改变的意思,被mutable关键字修饰的成员可以处于不断变化中,如mutable int _ct在 const 成员函数中修改。

3、const 修饰类

const修饰类对象表示该对象为常量对象,其中的任何成员都不能被修改。对于对象指针和对象引用也是一样。

const修饰的对象,该对象的任何非const成员函数都不能被调用,因为任何非const成员函数会有修改成员变量的企图。


class AAA
{
public:
	AAA() {};
	~AAA() {};
	void func1() {};
	void func2() const {};
}; 


int main()
{
	const AAA aObj;
	aObj.func1(); ×
    aObj.func2(); 正确

	const AAA* bObj = new AAA();
	bObj->func1(); ×
    bObj->func2(); 正确

	return 0;
}

 

参考源:为什么const对象只能调用const成员函数,而不能调用非const成员函数?


四、const类型转换为非const类型方法

1、const与非const类型赋值问题

  • 非指针类型
const int a = 10;
int b = 20;

b = a; // 正确
a = b; // Error!
---------------
// Error
const int a;
a = 10;

-----------------------------
	int a = 1;
	const int b = 2;

	int c = a;
	const int d = a;

	int e = b;
	const int f = b;

	c = a;
	c = b;
// 	d = a; //Error
// 	d = b; //Error
-----------------------------
  •  指针类型
const int* a  =  new int(10);
int * b = new int(10);

a = b; // 没问题
b = a; // 编译报错 不能从 const int* 转换程 int*

// 下面这种情况不会报错,但可能造成程序崩溃
b = (int*)a;
// ....
*b = 20; // 此处修改了b和a共同指向的地址的内容,程序崩溃

//原因: 
//const int * 表示,指向常量int 类型的指针,即指向的这块内存的内容,不可以修改。(但指针本身可以修改)。
//int * 表示,指向非常量int类型的指针,指针这块内存的内容可修改,指针本身可修改。
//const int* 已经限制此地址内容,不可修改。这时,却让 int * 指针指向这块地址,而使用 int * 指针,表示此地址内容可修改。那么到底可不可以改?
//接上,因此逻辑冲突,编译器报错。

-----------------------------
	int a = 1;
	const int b = 1;

	const int* c = &a;
	int * d = &a;

	const int* e = &b;
	int * f = (int*)&b; 

	c = &a;
	c = &b;
	d = &a;
	//d = &b; //Error
-----------------------------


2、转换方法: 

 用法:const_cast <type_id>  (expression) 

该运算符用来修改类型的const或volatile属性。除了const 或volatile修饰之外, type_id和expression的类型是一样的。

常量指针被转化成非常量指针,并且仍然指向原来的对象;

常量引用被转换成非常量引用,并且仍然指向原来的对象;

常量对象被转换成非常量对象。

通过const_cast运算符,也只能将const type*转换为type*,将const type&转换为type&。

也就是说源类型和目标类型除了const属性不同,其他地方完全相同。

#include<iostream>
using namespace std;
void ConstTest1()
{
	/*volatile*/ const int a = 5;
	int *p;
	p = const_cast<int*>(&a);
	(*p)++;
	cout << a << endl;
	cout << *p << endl;

}

void ConstTest2() 
{
	int i;
	cout << "please input a integer:";
	cin >> i;
	const int a = i;
	int &r = const_cast<int &>(a);
	r++;
	cout << a << endl;
}

int main() 
{
	ConstTest1();
	ConstTest2();
	return 0;
}

输出:
5
6
输入7
输出8

 

 

 

 

 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值