零. const 出现的原因
编写一个处理基本类型(如,int)的函数时,要选择是传递int类型的值还是传递指向int的指针。通常都是直接传递数值,只有程序需要在函数中改变该数值时,才会传递指针。对于数组别无选择,必须传递指针,因为这样做效率高。如果一个函数按值传递数组,则必须分配足够的空间来储存原数组的副本,然后把原数组所有的数据拷贝至新的数组中。如果把数组的地址传递给函数,让函数直接处理原数组则效率要高。
传递地址会导致一些问题。C通常都按值传递数据,因为这样做可以保证数据的完整性。如果函数使用的是原始数据的副本,就不会意外修改原始数据。但是,处理数组的函数通常都需要使用原始数据,因此这样的函数可以修改原数组。有时,这正是我们需要的。例如,下面的函数给数组的每个元素都加上一个相同的值:
void add_to(double ar[], int n, double val)
{
int i;
for (i = 0; i < n; i++)
ar[i] += val;
}
因此,调用该函数后,prices数组中的每个元素的值都增加了2.5:
add_to(prices, 100, 2.50);
该函数修改了数组中的数据。之所以可以这样做,是因为函数通过指针直接使用了原始数据。
然而,其他函数并不需要修改数据。例如,下面的函数计算数组中所有元素之和,它不用改变数组的数据。但是,由于ar实际上是一个指针,所以编程错误可能会破坏原始数据。例如,下面示例中的ar[i]++会导致数组中每个元素的值都加1:
int sum(int ar[], int n) // 错误的代码
{
int i;
int total = 0;
for( i = 0; i < n; i++)
total += ar[i]++; //错误递增了每个元素的值
return total;
}
一. const的概念:
const是一个常量关键字,主要是为了防止所修饰对象被修改。
我们在定义一个变量时,如果想要防止这个变量被修改,可以
用const来修饰这个变量。也就是说,被const修饰过的变量或者
函数,不能对其进行修改,否则,编译器就会报错。
C90标准新增了const关键字,用于限定一个变量为只读 。其声明如下:
const int MONTHS = 12; // MONTHS在程序中不可更改,值为12
这使得MONTHS成为一个只读值。也就是说,可以在计算中使用MONTHS,可以打印MONTHS,但是不能更改MONTHS的值。
用const类型限定符声明的是变量,不是常量。
我们通常用类型和存储类别来描述一个变量。C90 还新增了两个属性:恒常性(constancy)和易变性(volatility)。这两个属性可以分别用关键字 const 和 volatile 来声明,以这两个关键字创建的类型是限定类型(qualified type)。C99标准新增了第3个限定符:restrict,用于提高编译器优化。
二. 对全局数据使用const
使用全局变量是一种冒险的方法,因为这样做暴露了数据,程序的任何部分都能更改数据。如果把数据设置为 const,就可避免这样的危险,因此用 const 限定符声明全局数据很合理。可以创建const变量、const数组和const结构。
#define PI1 3.14
const double PI2 = 3.14;
const常量与define宏定义的区别:
1)处理阶段不同:
define是在预处理阶段,define常量从未被编译器看见,因为在预处理截断就已经替换了;
const常量在编译阶段使用。
2)类型和安全检查不同
define没有类型,不做任何检查,仅仅是字符替换,没有类型安全检查,并且在字符替换时可能会产生意料不到的错误
const常量有明确的类型,在编译阶段会进行类型检查;
3)存储方式不同
define是字符替换,有多少地方使用,就会替换多少次,不会分配内存;
编译器通常不会为const常量分配空间,只是将它们保存在符号表内,使他们成为一个编译期间的一个常量,没有读取内存的操作,效率也很高;
三. const的修饰
1、修饰常量时:
const int temp1; //temp1为常量,不可变
int const temp2; //temp2为常量,不可变
2. 修饰数组时:
const int days[MONTHS] = {31,28,31,30,31,30,31,31,30,31,30,31};
如果程序稍后尝试改变数组元素的值,编译器将生成一个编译期错误消息:
days[9] = 44; /* 编译错误 */
2、修饰指针时:
主要看const在*的前后,在前则指针指向的内容为常量,在后则指针本身为常量;
const int *ptr; //*ptr为常量;
int const *ptr; //*ptr为常量;
int* const ptr; //ptr为常量;
const int * const ptr; //*ptr、ptr均为常量;
例子:
unsigned int * const ptr的应用场景:
#define GPIOC_DR (*(vuint16_t *) (0xE221U))
#define GPIOE_DR (*(vuint16_t *) (0xE241U)
#define COIL_ENABLE_CONTROL_BIT_MASKS { 0x2000u, 0x0008u, 0x0004u }
unsigned int * const ptrCoilEnableControlPort[ 3 ] = { (unsigned int *) &GPIOC_DR, (unsigned int *) &GPIOE_DR, (unsigned int *) &GPIOE_DR };
const unsigned int CoilEnableControlBitMask[ 3 ] = COIL_ENABLE_CONTROL_BIT_MASKS;
//byCoilId = 0,1,2
if( ptrCoilEnableControlPort[ byCoilId ] )
{
*( ptrCoilEnableControlPort[ byCoilId ] ) |= CoilEnableControlBitMask[ byCoilId ];
}
const int *ptr的应用场景:
//将源字符串加const,表明其为输入参数
char* strcat(char* strDest , const char* strSrc)
{
//后文return address,故不能放在assert断言之后声明address
char* address=strDest;
assert( (strDest!=NULL)&&(strSrc!=NULL) );//对源地址和目的地址加非0断言
while(*address)//是while(*strDest!=’\0’)的简化形式
{
//若使用while(*strDest++),则会出错,因为循环结束后strDest还会执行一次++,
//那么strDest将指向'\0'的下一个位置。/所以要在循环体内++;因为要使*strDest最后指
//向该字符串的结束标志’\0’。
address++;
}
while(*address++=*strSrc++)
{
NULL;//该循环条件内可以用++,
}//此处可以加语句*strDest=’\0’;无必要
return strDest;//为了实现链式操作,将目的地址返回
}
四. const 的应用场景
1. 定义不能被修改的变量。
2. 定义不能被修改的数组。
3. 对形式参数用const(对输入参数用const,不允许改变输入参数的值)
4. 修饰函数返回值,防止返回值被改变
const int fun();
接收返回值的变量也必须加const
const int a = fun(); //接收的变量也要是const的,int a = fun()是错误的