C#为什么不能像C/C++一样的支持函数只读传参

C#为什么不能像C/C++一样的支持函数只读传参?

(1)可以定义 const 常量

这个问题其实问的人挺多的,我自己也经常想实现这个功能,但是发现总是那么的不尽人意。

(2)const 可以修饰函数的参数、返回值.

有些人倒是给出了一下答案,但是都不能很好的解决像C/C++一样的支持函数只读传参这个问题。

详细内容:

其实这块我觉得我们可能有个误区。就是我们为什么要这样做呢?这样做的目的是什么?对了就是这个。答案也在这里面。关键就在这目的上。我们用C/C++可能有 function(Data const * const pcData),但是在C#里面没有了。感觉似乎少了点什么。对就是少了点什么,我一直这么觉着。其实我们仔细想一想,C/C++这么写是什么目的?这么写的目的是不允许(限制)写接口的人在实现接口的时候在接口类部修改参数内容,对了,就是这个目的。可是写接口的人不管怎么写,调用接口的人怎么知道呢,他可能看了头文件里面的函数声明,哦,这个函数是安全的,不会修改我的参数。关键点来了。其实C/C++的const参数限定,是在写代码的时候编译期限定的,也就是说编译器在你编译方法的时候就帮你检查了方法是否有修改参数的行为。那么C#呢?他就让写方法的人去决定是否会在内部修改参数内容。C#有四种参数修饰符:in、params、ref、out。这四种各有各的用途,尤其是ref和out,out专门用来修改参数的,那么当我们没有修饰符的修改参数的时候,我们应该默认是不修改函数参数的。目前本人只能理解到这种地步,希望有更深层次的理解的朋友给出帮助。谢谢!!!

1、什么是const?

常类型是指使用类型修饰符const说明的类型,常类型的变量或对象的值是不能被更新的。(当然,我们可以偷梁换柱进行更新:)

 

2、为什么引入const?

const 推出的初始目的,正是为了取代预编译指令,消除它的缺点,同时继承它的优点。

 

3、cons有什么主要的作用?

(1)可以定义const常量,具有不可变性。例如:

const int Max=100; int Array[Max];

(2)便于进行类型检查,使编译器对处理内容有更多了解,消除了一些隐患。例如: void f(const int i) { .........} 编译器就会知道i是一个常量,不允许修改;(3)可以避免意义模糊的数字出现,同样可以很方便地进行参数的调整和修改。同宏定义一样,可以做到不变则已,一变都变!如(1)中,如果想修改Max的内容,只需要:const int Max=you want;即可!

(4)可以保护被修饰的东西,防止意外的修改,增强程序的健壮性。还是上面的例子,如果在函数体内修改了i,编译器就会报错;例如:

void f(const int i) { i=10;//error! }

(5)为函数重载提供了一个参考。

class A { ......

void f(int i) {......} //一个函数

void f(int i) const {......} //上一个函数的重载 ......

};

(6)可以节省空间,避免不必要的内存分配。例如:

#define PI 3.14159 //常量宏

const doulbe Pi=3.14159; //此时并未将Pi放入ROM中 ......

double i=Pi; //此时为Pi分配内存,以后不再分配!

double I=PI; //编译期间进行宏替换,分配内存

double j=Pi; //没有内存分配

double J=PI; //再进行宏替换,又一次分配内存!

const定义常量从汇编的角度来看,只是给出了对应的内存地址,而不是象#define一样给出的是立即数,所以,const定义的常量在程序运行过程中只有一份拷贝,而#define定义的常量在内存中有若干个拷贝。

(7)提高了效率。编译器通常不为普通const常量分配存储空间,而是将它们保存在符号表中,这使得它成为一个编译期间的常量,没有了存储与读内存的操作,使得它的效率也很高。

 

4、如何使用const?

(1)修饰一般常量一般常量是指简单类型的常量。这种常量在定义时,修饰符const可以用在类型说明符前,也可以用在类型说明符后。例如:

int const x=2; 或 const int x=2;

(2)修饰常数组定义或说明一个常数组可采用如下格式:

int const a[5]={1, 2, 3, 4, 5};

const int a[5]={1, 2, 3, 4, 5};

(3)修饰常对象常对象是指对象常量,定义格式如下:

class A; const A a;

A const a; 定义常对象时,同样要进行初始化,并且该对象不能再被更新,修饰符const可以放在类名后面,也可以放在类名前面。

(4)修饰常指针

const int *A; //const修饰指向的对象,A可变,A指向的对象不可变

int const *A; //const修饰指向的对象,A可变,A指向的对象不可变

int *const A; //const修饰指针A, A不可变,A指向的对象可变

const int *const A;//指针A和A指向的对象都不可变

(5)修饰常引用使用const修饰符也可以说明引用,被说明的引用为常引用,该引用所引用的对象不能被更新。其定义格式如下:

const double & v;

(6)修饰函数的常参数 const修饰符也可以修饰函数的传递参数,格式如下:

void Fun(const int Var); 告诉编译器Var在函数体中的无法改变,从而防止了使用者的一些无意的或错误的修改。

(7)修饰函数的返回值: const修饰符也可以修饰函数的返回值,是返回值不可被改变,格式如下:

const int Fun1(); const MyClass Fun2();

(8)修饰类的成员函数: const修饰符也可以修饰类的成员函数,格式如下:

class ClassName {

public:

int Fun() const; .....

};这样,在调用函数Fun时就不能修改类里面的数据

(9)在另一连接文件中引用const常量

extern const int i;//正确的引用

extern const int j=10;//错误!常量不可以被再次赋值另外,还要注意,常量必须初始化!例如: const int i=5;

 

5、几点值得讨论的地方:

(1)const究竟意味着什么?

说了这么多,你认为const意味着什么?一种修饰符?接口抽象?一种新类型?也许都是,在Stroustup最初引入这个关键字时,只是为对象放入ROM做出了一种可能,对于const对象,C++既允许对其进行静态初始化,也允许对他进行动态初始化。理想的const对象应该在其构造函数完成之前都是可写的,在析够函数执行开始后也都是可写的,换句话说,const对象具有从构造函数完成到析够函数执行之前的不变性,如果违反了这条规则,结果都是未定义的!虽然我们把const放入ROM中,但这并不能够保证const的任何形式的堕落,我们后面会给出具体的办法。无论const对象被放入ROM中,还是通过存储保护机制加以保护,都只能保证,对于用户而言这个对象没有改变。换句话说,废料收集器(我们以后会详细讨论,这就一笔带过)或数据库系统对一个const的修改怎没有任何问题。

(2)位元const V.S. 抽象const?

对于关键字const的解释有好几种方式,最常见的就是位元const 和抽象const。下面我们看一个例子: class A { public: ...... A f(const A& a); ...... }; 如果采用抽象const进行解释,那就是f函数不会去改变所引用对象的抽象值,如果采用位元const进行解释,那就成了f函数不会去改变所引用对象的任何位元。我们可以看到位元解释正是c++对const问题的定义,const成员函数不被允许修改它所在对象的任何一个数据成员。为什么这样呢?因为使用位元const有2个好处:最大的好处是可以很容易地检测到违反位元const规定的事件:编译器只用去寻找有没有对数据成员的赋值就可以了。另外,如果我们采用了位元const,那么,对于一些比较简单的const对象,我们就可以把它安全的放入ROM中,对于一些程序而言,这无疑是一个很重要的优化方式。(关于优化处理,我们到时候专门进行讨论)当然,位元const也有缺点,要不然,抽象const也就没有产生的必要了。首先,位元const的抽象性比抽象const的级别更低!实际上,大家都知道,一个库接口的抽象性级别越低,使用这个库就越困难。其次,使用位元const的库接口会暴露库的一些实现细节,而这往往会带来一些负面效应。所以,在库接口和程序实现细节上,我们都应该采用抽象const。有时,我们可能希望对const做出一些其它的解释,那么,就要注意了,目前,大多数对const的解释都是类型不安全的,这里我们就不举例子了,你可以自己考虑一下,总之,我们尽量避免对const的重新解释。

(3)放在类内部的常量有什么限制?

看看下面这个例子:

class A {

private:

const int c3 = 7; // ???

static int c4 = 7; // ???

static const float c5 = 7; // ??? ......

};

你认为上面的3句对吗?呵呵,都不对!使用这种类内部的初始化语法的时候,常量必须是被一个常量表达式初始化的整型或枚举类型,而且必须是static和const形式。这显然是一个很严重的限制!那么,我们的标准委员会为什么做这样的规定呢?一般来说,类在一个头文件中被声明,而头文件被包含到许多互相调用的单元去。但是,为了避免复杂的编译器规则,C++要求每一个对象只有一个单独的定义。如果C++允许在类内部定义一个和对象一样占据内存的实体的话,这种规则就被破坏了。

(4)如何初始化类内部的常量?

一种方法就是static 和 const 并用,在内部初始化,如上面的例子;另一个很常见的方法就是初始化列表:

class A {

public:

A(int i=0):test(i) {}

private:

const int i;

};还有一种方式就是在外部初始化,例如:

class A {

public:

A() {}

private:

static const int i;//注意必须是静态的!

};

const int A::i=3;

(5)常量与数组的组合有什么特殊吗?我们给出下面的代码:

const int size[3]={10,20,50};

int array[size[2]];

有什么问题吗?对了,编译通不过!为什么呢?

Const可以用于集合,但编译器不能把一个集合存放在它的符号表里,所以必须分配内存。在这种情况下,const意味着"不能改变的一块存储"。然而,其值在编译时不能被使用,因为编译器在编译时不需要知道存储的内容。自然,作为数组的大小就不行了:)你再看看下面的例子:

class A {

public:

A(int i=0):test[2]({1,2}) {}//你认为行吗?

private:

const int test[2];

};

vc6下编译通不过,为什么呢?关于这个问题,前些时间,njboy问我是怎么回事?我反问他:"你认为呢?"他想了想,给出了一下解释,大家可以看看:我们知道编译器堆初始化列表的操作是在构造函数之内,显式调用可用代码之前,初始化的次序依据数据声明的次序。初始化时机应该没有什么问题,那么就只有是编译器对数组做了什么手脚!其实做什么手脚,我也不知道,我只好对他进行猜测:编译器搜索到test发现是一个非静态的数组,于是,为他分配内存空间,这里需要注意了,它应该是一下分配完,并非先分配test[0],然后利用初始化列表初始化,再分配test[1],这就导致数组的初始化实际上是赋值!然而,常量不允许赋值,所以无法通过。呵呵,看了这一段冠冕堂皇的话,真让我笑死了!njboy别怪我揭你短呀:)我对此的解释是这样的:C++标准有一个规定,不允许无序对象在类内部初始化,数组显然是一个无序的,所以这样的初始化是错误的!对于他,只能在类的外部进行初始化,如果想让它通过,只需要声明为静态的,然后初始化。这里我们看到,常量与数组的组合没有什么特殊!一切都是数组惹的祸!

(6)this指针是不是const类型的?

this指针是一个很重要的概念,那该如何理解她呢?也许这个话题太大了,那我们缩小一些:this指针是个什么类型的?这要看具体情况:如果在非const成员函数中,this指针只是一个类类型的;如果在const成员函数中,this指针是一个const类类型的;如果在volatile成员函数中,this指针就是一个volatile类类型的。

(7)const到底是不是一个重载的参考对象?

先看一下下面的例子:

class A {

......

void f(int i) {......}//一个函数

void f(int i) const {......}//上一个函数的重载

......

}; 上面是重载是没有问题的了,那么下面的呢?

class A {

......

void f(int i) {......}//一个函数

void f(const int i) {......}//?????

......

}; 这个是错误的,编译通不过。那么是不是说明内部参数的const不予重载呢?再看下面的例子:

class A {

......

void f(int& ) {......}//一个函数

void f(const int& ) {......}//?????

......

}; 这个程序是正确的,看来上面的结论是错误的。为什么会这样呢?这要涉及到接口的透明度问题。按值传递时,对用户而言,这是透明的,用户不知道函数对形参做了什么手脚,在这种情况下进行重载是没有意义的,所以规定不能重载!当指针或引用被引入时,用户就会对函数的操作有了一定的了解,不再是透明的了,这时重载是有意义的,所以规定可以重载。

(8)什么情况下为const分配内存?

以下是我想到的可能情况,当然,有的编译器进行了优化,可能不分配内存。

A、作为非静态的类成员时;

B、用于集合时;

C、被取地址时;

D、在main函数体内部通过函数来获得值时;

E、const的 class或struct有用户定义的构造函数、析构函数或基类时;。

F、当const的长度比计算机字长还长时;

G、参数中的const;

H、使用了extern时。不知道还有没有其他情况,欢迎高手指点:)

(9)临时变量到底是不是常量?

很多情况下,编译器必须建立临时对象。像其他任何对象一样,它们需要存储空间而且必须被构造和删除。区别是我们从来看不到编译器负责决定它们的去留以及它们存在的细节。对于C++标准草案而言:临时对象自动地成为常量。因为我们通常接触不到临时对象,不能使用与之相关的信息,所以告诉临时对象做一些改变有可能会出错。当然,这与编译器有关,例如:vc6、vc7都对此作了扩展,所以,用临时对象做左值,编译器并没有报错。

(10)与static搭配会不会有问题?假设有一个类:

class A {

public:

......

static void f() const { ......}

......

}; 我们发现编译器会报错,因为在这种情况下static不能够与const共存!为什么呢?因为static没有this指针,但是const修饰this指针,所以...

(11)如何修改常量?

有时候我们却不得不对类内的数据进行修改,但是我们的接口却被声明了const,那该怎么处理呢?我对这个问题的看法如下:

1)标准用法:

mutable class A {

public:

A(int i=0):test(i) { }

void SetValue(int i)const { test=i; }

private: mutable int test;//这里处理!

};

2)强制转换:

const_cast class A {

public:

A(int i=0):test(i) { }

void SetValue(int i)const {

const_cast (test)=i;

}//这里处理!

private:

int test;

};

3)灵活的指针:

int* class A {

public:

A(int i=0):test(i) { }

void SetValue(int i)const { *test=i; }

private:

int* test; //这里处理!

};

4)未定义的处理

class A {

public:

A(int i=0):test(i) { }

void SetValue(int i)const {

int *p=(int*)&test; *p=i;

}//这里处理!

private:

int test;

};注意,这里虽然说可以这样修改,但结果是未定义的,避免使用!

5)内部处理:this指针

class A {

public:

A(int i=0):test(i) { }

void SetValue(int i)const {

((A*)this)->test=i;

}//这里处理!

private:

int test;

};

6)最另类的处理:空间布局

class A {

public:

A(int i=0):test(i),c('a') { }

private:

char c;

const int test;

};

int main()

{

A a(3);

A* pa=&a;

char* p=(char*)pa;

int* pi=(int*)(p+4);//利用边缘调整

*pi=5;//此处改变了test的值!

return 0;

}

虽然我给出了6中方法,但是我只是想说明如何更改,但出了第一种用法之外,另外5种用法,我们并不提倡,不要因为我这么写了,你就这么用,否则,我真是要误人子弟了:)

(12)最后我们来讨论一下常量对象的动态创建。既然编译器可以动态初始化常量,就自然可以动态创建,例如:

const int* pi=new const int(10); 这里要注意2点:

1)const对象必须被初始化!所以(10)是不能够少的。

2)new返回的指针必须是const类型的。 那么我们可不可以动态创建一个数组呢? 答案是否定的,因为new内置类型的数组,不能被初始化。 这里我们忽视了数组是类类型的,同样对于类内部数组初始化我们也做出了这样的忽视,因为这涉及到数组的问题,我们以后再讨论。

 

 

 

 

 

 

 

c语言中的const关键字

1, 基本的const

1.1 const和变量的初始化

    如果在定义const自动变量时没有进行初始化,

那么就没法直接进行初始化,而只能通过指针间接进行初始化。

 

int

main()   

{

    const int a;

    a = 9;       

    //这里无法通过编译

    //错误:向只读变量 'a' 赋值

    //但如果写成这样 const int a = 9;将顺利通过编译。   

 

    return 0;

}

 

1.2 以下定义是相同的

 

int const a = 9;

const int a = 9;

 

记住:const和基本自动变量使用,定义时可以交换位置。

    在和指针一起定义某个变量时,不能交换。

 

 

1.3 const并非无法修改

    无法直接修改const的变量,但可以通过指针的方式

间接修改。

 

void

test1(void)

{

    int *pi;

    int const a = 9;

     

    printf("int const a = %dn", a);

    pi = &a;

    *pi = 2;

    printf("haha, I change it a = %dn", a);   

}

 

 

2, const 和指针

    const和指针在一起使用时,容易搞混,

    在网上找到一个识别的方法觉得用起来很不错:

    舍弃法识破const。

    1)首先舍弃const,得到一个普通的声明;

    2)若有多个const从最左边的const开始去。

    3)然后舍弃const右面的const标志以及其它的关键字;

    4)接下来就是将const右面所有的*和变量用一个新变量代替,

        单个字符不用被取代,那么这个新变量就是const作用对象。

 

下面先给出这几个表达式的解释,后面用例子说明。

const int *A;          //修饰指向的对象,A可变,A指向的对象不可变

int const *A;       //修饰指向的对象,A可变,A指向的对象不可变

int *const A;       //修饰指针A, A不可变,A指向的对象可变

const int *const A; //指针A和A指向的对象都不可变

 

2.1 const int *A;和 int const *A;

    按照上面的3条,这两个的表达式的作用应该是一样的。

    const的作用对象应该是*A这个整体。也就是作用于A指向的变量的值。

     

61 void

62 test4(void)

63 {

64         int num=12;

65         const int *A=#

66         (*A)++;     //error: 令只读位置自增

67         printf("result=%dn",*A);

68 }

    61-68行可以看出,const int *A的确修饰的是*A这个整体,也就是

指针指向的值。 

    下面的这个例子说明了,虽然无法改变*A的值,但是可以改变A指针本身。

 

71 void

72 test5(void)

73 {

74     int a[] = {1,2,3,4,5};

75     const int *ar = &a;

76

77     *ar++;                  //ok

78     printf("result=%dn",*ar);    //result=2

79 }

     

2.2 int *const a; 和 const *int a;

     

    const *int a;     //这样的定义格式是错误的,指针不知道是什么类型。

     

    int *const a;

    按照上面的规则我可以判断,该const是修饰的a,而a被定义为一个指针。

所以他限定的对象是a这个指针。也就是说a的指针值不能修改。

         

#include<stdio.h>

int

main(void)

{

    int a[] = {1,2,3,4,5};

    int *const ar = a;

 

    *ar++;                  //error

    //若是这样写,就对了:

    //*ar = 9;            // ok

    printf("result=%dn",*ar); //result=2

        return 0;

}

编译无法通过,出现错误:

test2.c: In function 'main':

test2.c:9: 错误:令只读变量 'ar' 自增

 

2.3 const int *const a; //指针A和A指向的对象都不可变

    通过分析知道,该定义中const限制了*a和a。也就是这

两个(作为整体)都不能改变。

 

1 #include<stdio.h>

2

3 int

4 main(void)

5 {

6     int a[] = {1,2,3,4,5};

7     const int *const ar = a;

8

9     //*ar++;                  //error

10     *ar = 9;            // error

11     printf("result=%dn",*ar); //result=2

12     return 0;

13 }

     

 

3, 作为参数时的const

17 void

18 testpc(const char *s, const int *a)

19 {

20     if (*a == 3) {

21         a = 0;      //改变其地址允许

22         s[1] = "a"; //改变*s的值,出错

23     }

24 }

 

    根据规则,这样的赋值是错误的,但如果改变其地址却可以。

const int *a;的意思是不能改变 *a这个整体的值。

 

 

 

 

 

      const是一个C语言的关键字,它限定一个变量不允许被改变。使用const在一定程度上可以提高程序的健壮性,另外,在观看别人代码的时候,清晰理解const所起的作用,对理解对方的程序也有一些帮助。

  虽然这听起来很简单,但实际上,const的使用也是c语言中一个比较微妙的地方,微妙在何处呢?请看下面几个问题。

    

    问题:const变量 & 常量  

    为什么我象下面的例子一样用一个const变量来初始化数组,ANSI C的编译器会报告一个错误呢?

  const int n = 5;

  int a[n];

    答案与分析:

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

    2)、注意:在ANSI C中,这种写法是错误的,因为数组的大小应该是个常量,而const int n,n只是一个变量(常量 != 不可变的变量,但在标准C++中,这样定义的是一个常量,这种写法是对的),实际上,根据编译过程及内存分配来看,这种用法本来就应该是合理的,只是 ANSI C对数组的规定限制了它。

    3)、那么,在ANSI C 语言中用什么来定义常量呢?答案是enum类型和#define宏,这两个都可以用来定义常量。

    

  问题:const变量 & const 限定的内容

  下面的代码编译器会报一个错误,请问,哪一个语句是错误的呢?

  typedef char * pStr;

  char string[4] = "abc";

  const char *p1 = string;

  const pStr p2 = string;

  p1++;

  p2++;

    答案与分析:

    问题出在p2++上。

    1)、const使用的基本形式: const char m; 限定m不可变。

    2)、替换1式中的m, const char *pm; 限定*pm不可变,当然pm是可变的,因此问题中p1++是对的。

    3)、替换1式char, const newType m; 限定m不可变,问题中的charptr就是一种新类型,因此问题中p2不可变,p2++是错误的。

 

    问题:const变量 & 字符串常量

    请问下面的代码有什么问题?

  char *p = "i'm hungry!";

  p[0]= 'I';

    答案与分析:

    上面的代码可能会造成内存的非法写操作。分析如下, "i'm hungry"实质上是字符串常量,而常量往往被编译器放在只读的内存区,不可写。p初始指向这个只读的内存区,而p[0] = 'I'则企图去写这个地方,编译器当然不会答应。

金沙官网线上,    

    问题:const变量 & 字符串常量2

    请问char a[3] = "abc" 合法吗?使用它有什么隐患?

    答案与分析:

    在标准C中这是合法的,但是它的生存环境非常狭小;它定义一个大小为3的数组,初始化为"abc"。注意,它没有通常的字符串终止符'',因此这个数组只是看起来像C语言中的字符串,实质上却不是,因此所有对字符串进行处理的函数,比如strcpy、printf等,都不能够被使用在这个假字符串上。

    

    问题5:const & 指针

    类型声明中const用来修饰一个常量,有如下两种写法,那么,请问,下面分别用const限定不可变的内容是什么?

    1)、const在前面

  const int nValue; //nValue是const

  const char *pContent; //*pContent是const, pContent可变

  const (char *) pContent;//pContent是const,*pContent可变

  char* const pContent; //pContent是const,*pContent可变

  const char* const pContent; //pContent和*pContent都是const

    2)、const在后面,与上面的声明对等

  int const nValue; // nValue是const

  char const * pContent;// *pContent是const, pContent可变

  (char *) const pContent;//pContent是const,*pContent可变

  char* const pContent;// pContent是const,*pContent可变

  char const* const pContent;// pContent和*pContent都是const

    答案与分析:

    const和指针一起使用是C语言中一个很常见的困惑之处,在实际开发中,特别是在看别人代码的时候,常常会因为这样而不好判断作者的意图,下面讲一下我的判断原则:

    沿着*号划一条线,如果const位于*的左侧,则const就是用来修饰指针所指向的变量,即指针指向为常量;如果const位于*的右侧,const就是修饰指针本身,即指针本身是常量。你可以根据这个规则来看上面声明的实际意义,相信定会一目了然。

    另外,需要注意:对于const (char *) ; 因为char *是一个整体,相当于一个类型(如 char),因此,这是限定指针是const。

    

  另=======

 

  const用于函数时出现三个位置:

  例如:

  const returnVal function (const list_array)const;

 

  第一个const意思是:返回值是常量

 

  第二个const意思是:函数过程中不能修改list_array的值

  第三个const意思是:函数过程不能隐式的修改function参数的值

    

  ===

    

  zzhttp://publishblog.blogchina.com/blog/tb.b?diaryID=3217823

    

  const char*, char const*, char*const的区别问题几乎是C++面试中每次都会有的题目。

 

  Bjarne在他的The C++ Programming Language里面给出过一个助记的方法:把一个声明从右向左读。

  char * const cp; ( * 读成 pointer to ) :cp is a const pointer to char

  const char * p; :p is a pointer to const char;

  char const * p;

  同上因为C++里面没有const*的运算符,所以const只能属于前面的类型。

    

    

  另:下面定义的一个指向字符串的常量指针:

    char * const prt1 = stringprt1;

    其中,ptr1是一个常量指针。因此,下面赋值是非法的。 ptr1 = stringprt2;

    而下面的赋值是合法的: *ptr1 = "m";

    因为指针ptr1所指向的变量是可以更新的,不可更新的是常量指针ptr1所指的方向(别的字符串)。

 

 

    下面定义了一个指向字符串常量的指针:

    const * ptr2 = stringprt1;

    其中,ptr2是一个指向字符串常量的指针。ptr2所指向的字符串不能更新的,而ptr2是可以更新的。因此,

    *ptr2 = "x"; 是非法的,而: ptr2 = stringptr2; 是合法的。

    所以,在使用const修饰指针时,应该注意const的位置。定义一个指向字符串的指针常量和定义一个指向字符串常量的指针时,const修饰符的位置不同,前者const放在*和指针名之间,后者const放在类型说明符前。

 

 

 

 

 

 

 

 

 

constC**语言**中算是一个比较新的描述符,我们称之为常量修饰符,意即其所修饰

的对象为常量(immutable)。

 

我们来分情况看语法上它该如何被使用。

 

1、函数体内修饰局部变量。

例:

void func(){

const int a=0;

}

 

首先,我们先把const这个单词忽略不看,那么a是一个int类型的局部自动变量,

我们给它赋予初始值0。

 

然后再看const.

 

const作为一个类型限定词,和int有相同的地位。

const int a;

int const a;

是等价的。于是此处我们一定要清晰的明白,const修饰的对象是谁,是a,和int没

有关系。const 要求他所修饰的对象为常量,不可被改变,不可被赋值,不可作为

左值(l-value)。

这样的写法也是错误的。

const int a;

a=0;

这是一个很常见的使用方式:

const double pi=3.14;

程序的后面如果企图对pi再次赋值或者修改就会出错。

 

然后看一个稍微复杂的例子。

const int* p;

还是先去掉const 修饰符号。

注意,下面两个是等价的。

int* p;

int *p;

其实我们想要说的是,*p是int类型。那么显然,p就是指向int的指针

同理

const int* p;

其实等价于

const int (*p);

int const (*p);

即,*p是常量。也就是说,p指向的数据是常量。

于是

p+=8; //合法

*p=3; //非法,p指向的数据是常量。

 

那么如何声明一个自身是常量指针呢?方法是让const尽可能的靠近p;

int* const p;

const右面只有p,显然,它修饰的是p,说明p不可被更改。然后把const去掉,可以

看出p是一个指向 int形式变量的指针。

于是

p+=8; //非法

*p=3; //合法

 

再看一个更复杂的例子,它是上面二者的综合

const int* const p;

说明p自己是常量,且p指向的变量也是常量。

于是

p+=8; //非法

*p=3; //非法

 

const 还有一个作用就是用于修饰常量静态字符串。

例如:

const char* name=David;

如果没有const,我们可能会在后面有意无意的写name[4]='x'这样的语句,这样会

导致对只读内存区域的赋值,然后程序会立刻异常终止。有了 const,这个错误就

能在程序被编译的时候就立即检查出来,这就是const的好处。让逻辑错误在编译

期被发现。

 

const 还可以用来修饰数组

const char s[]=David;

与上面有类似的作用。

 

2、在函数声明时修饰参数

来看实际中的一个例子。

NAME

memmove -- copy byte string

 

LIBRARY

Standard C Library (libc, -lc)

 

SYNOPSIS

#include

 

void *

memmove(void *dst, const void *src, size_t len);

 

这是标准库中的一个函数,用于按字节方式复制字符串(内存)。

它的第一个参数,是将字符串复制到哪里去(dest),是目的地,这段内存区域必须

是可写。

它的第二个参数,是要将什么样的字符串复制出去,我们对这段内存区域只做读

取,不写。

于是,我们站在这个函数自己的角度来看,src 这个指针,它所指向的内存内所存

储的数据在整个函数执行的过程中是不变。于是src所指向的内容是常量。于是就

需要用const修饰。

例如,我们这里这样使用它。

const char* s=hello;

char buf[100];

memmove(buf,s,6); //这里其实应该用strcpy或memcpy更好

 

如果我们反过来写,

memmove(s,buf,6);

那么编译器一定会报错。事实是我们经常会把各种函数的参数顺序写反。事实是编

译器在此时帮了我们大忙。如果编译器静悄悄的不报错,(在函数声明处去掉

const即可),那么这个程序在运行的时候一定会崩溃。

 

这里还要说明的一点是在函数参数声明中const一般用来声明指针而不是变量本身。

例如,上面的size_t len,在函数实现的时候可以完全不用更改len的值,那么是否

应该把len也声明为常量呢?可以,可以这么做。我们来分析这么做有什么优劣。

如果加了const,那么对于这个函数的实现者,可以防止他在实现这个函数的时候修

改不需要修改的值(len),这样很好。

但是对于这个函数的使用者,

1。这个修饰符号毫无意义,我们可以传递一个常量整数或者一个非常量整数过

去,反正对方获得的只是我们传递的一个copy。

2。暴露了实现。我不需要知道你在实现这个函数的时候是否修改过len的值。

 

所以,const一般只用来修饰指针。

 

再看一个复杂的例子

int execv(const char *path, char *const argv[]);

着重看后面这个,argv.它代表什么。

如果去掉const,我们可以看出

char * argv[];

argv是一个数组,它的每个元素都是char *类型的指针。

如果加上const.那么const修饰的是谁呢?他修饰的是一个数组,argv[],意思就是

说这个数组的元素是只读的。那么数组的元素的是什么类型呢?是char *类型的指

针.也就是说指针是常量,而它指向的数据不是。

于是

argv[1]=NULL; //非法

argv[0][0]='a'; //合法

 

 

3、全局变量。

我们的原则依然是,尽可能少的使用全局变量。

我们的第二条规则则是,尽可能多的使用const。

如果一个全局变量只在本文件中使用,那么用法和前面所说的函数局部变量没有什

本文由金沙官网线上发布于编程,转载请注明出处:C#为什么不能像C/C++一样的支持函数只读传参

您可能还会对下面的文章感兴趣: