• 值类型和引用类型


    值类型和引用类型

    值类型

    值类型这个概念经常出现在类似C#,JAVA等编程语言的书籍中."值类型"直接将内存存储在栈内,由系统自动释放资源的数据类型. 与值类型相对应的有引用类型.C#语言中还对应指针类型.

    值类型编辑

    每一种编程语言的值类型都有一些非常细小的不同.下文所指的内容仅仅是.NET框架中C#编程语言的值类型定义.

    整体来说C#的值类型有:

    [1] 整型:Int; 长整型:long; 浮点型:float; 字符型:char; 布尔型:bool 枚举:enum 结构:struct 在C#中所有的值类型都继承自:System.ValueType 主要功能编辑 基于值类型的变量直接包含值。

    [2]  将一个值类型变量赋给另一个值类型变量时,将复制包含的值。这与引用类型变量的赋值不同,引用类型变量的赋值只复制对对象的引用,而不复制对象本身。 所有的值类型均隐式派生自SystemValueType。 与引用类型不同,不能从值类型派生出新的类型。但与引用类型相同的是,结构也可以实现接口。 与引用类型不同,值类型无法包含null值。但是,可以为 null 的类型功能允许值类型分配给null。 每种值类型均有一个隐式的默认构造函数来初始化该类型的默认值。 中值类型编辑 在使用 C# 中的局部变量之前,必须对其进行初始化。

    [3] int myInt; 那么在将其初始化之前,无法使用此变量。可使用下列语句将其初始化: myInt = new int(); 此语句是下列语句的等效语句: myInt = 0; 当然,可以用同一个语句进行声明和初始化,如下面示例所示: int myInt = new int(); - 或 - int myInt = 0; 使用new运算符时,将调用特定类型的默认构造函数并对变量赋以默认值。在上例中,默认构造函数将值0赋给了myInt。 对于用户定义的类型,使用new来调用默认构造函数。

    引用类型 引用类型 由类型的实际值引用(类似于指针)表示的数据类型。如果为某个变量分配一个引用类型,则该变量将引用(或“指向”)原始值。不创建任何副本。引用类型包括类、接口、委托和装箱值类型。

    “引用”(reference)是c++的一种新的变量类型,是对C的一个重要补充。

    它的作用是为变量起一个别名。假如有一个变量a,想给它起一个别名,可以这样写: int a;int &b=a; 这就表明了b是a的“引用”,即a的别名。

    经过这样的声明,使用a或b的作用相同,都代表同一变量。在上述引用中,&是“引用声明符”,并不代表地址。 不要理解为“把a的值赋给b的地址”。

    引用类型的数据存储在内存的堆中,而内存单元中只存放堆中对象的地址。声明引用并不开辟内存单元,b和a都代表同一变量单元。 注意:在声明引用变量类型时,必须同时使之初始化,即声明它代表哪一变量。在声明一个变量的引用之后,在本函数执行期间,该引用一直与其代表的变量相联系 ,不能再作为其他变量的别名。

    下面的用法不对: int a1,a2; int &b=a1; int &b=a2;//企图使b变成a2的别名(引用)是不行的。这样是错误的。

    我们可以把a2的值赋给b。 b=a2; 区别编辑 引用和指针的区别    

    看实例吧: 引用是C++中的概念,初学者容易把引用和指针混淆一起。 下面的程序中,n是m的一个引用(reference),m是被引用物(referent)。

    int m; int &n = m; n相当于m的别名(绰号),对n的任何操作就是对m的操作。 所以n既不是m的拷贝,也不是指向m的指针,其实n就是m它自己。 引用的规则编辑

    (1)引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。

    (2)不能有NULL引用,引用必须与合法的存储单元关联(指针则可以是NULL)。

    (3)一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。

    以下示例程序中,k被初始化为i的引用。 语句k = j并不能将k修改成为j的引用,只是把k的值改变成为6。 由于k是i的引用,所以i的值也变成了6。

    int i = 5; int j = 6; int &k = i; k = j; // k和i的值都变成了6; 主要功能编辑 引用的主要功能:传递函数的参数和返回值。

    C++语言中,函数的参数和返回值的传递方式有三种:值传递、指针传递和引用传递。 以下是"值传递"的示例程序。 由于Func1函数体内的x是外部变量n的一份拷贝,改变x的值不会影响n, 所以n的值仍然是0。

    void Func1(int x) { x = x + 10; } ... int n = 0; Func1(n); cout << "n = " << n =" 0" n =" 0;" n = " << n << endl; // n = 10 以下是" size="14"> void Func3(int &x) { x = x + 10; } ... int n = 0; Func3(n); cout << "n = " << n =" 10">

    (1) 在实际的程序中,引用主要被用做函数的形式参数--通常将类对象传递给一个函数.引用必须初始化. 但是用对象的地址初始化引用是错误的,我们可以定义一个指针引用。 1 int ival = 1092; 2 int &re = ival; //ok 3 int &re2 = &ival; //错误 4 int *pi = &ival; 5 int *&pi2 = pi; //ok

    (2) 一旦引用已经定义,它就不能再指向其他的对象.这就是为什么它要被初始化的原因。 (

    3) const引用可以用不同类型的对象初始化(只要能从一种类型转换到另一种类型即可),也可以是不可寻址的值,如文字常量。例如 double dval = 3.14159; //下3行仅对const引用才是合法的 const int &ir = 1024; const int &ir2 = dval; const double &dr = dval + 1.0; 上面,同样的初始化对于非const引用是不合法的,将导致编译错误。原因有些微妙,需要适当做些解释。 引用在内部存放的是一个对象的地址,它是该对象的别名。

    对于不可寻址的值,如文字常量,以及不同类型的对象,编译器为了实现引用,必须生成一个临时对象,引用实际上指向该对象,但用户不能访问它。

    例如: double dval = 23; const int &ri = dval; 编译器将其转换为: int tmp = dval; // double -> int const int &ri = tmp; 同理:上面代码 double dval = 3.14159; //下3行仅对const引用才是合法的 const int &ir = 1024; const int &ir2 = dval; const double &dr = dval + 1.0; 内部转化为: double dval = 3.14159; //不可寻址,文字常量 int tmp1 = 1024; const int &ir = tmp1; //不同类型 int tmp2 = dval;//double -> int const int &ir2 = tmp2; //另一种情况,不可寻址 double tmp3 = dval + 1.0; const double &dr = tmp3;

    (4) 不允许非const引用指向需要临时对象的对象或值,即,编译器产生临时变量的时候引用必须为const!!!!切记!! int iv = 100; int *&pir = &iv;//错误,非const引用对需要临时对象的引用 int *const &pir = &iv;//ok const int ival = 1024; int *&pi_ref = &ival; //错误,非const引用是非法的 const int *&pi_ref = &ival; //错误,需要临时变量,且引用的是指针,而pi_ref是一个非常量指针 const int * const &pi_ref = &ival; //正确 //补充 const int *p = &ival; const int *&pi_ref = p; //正确

    (5) 对于const int *const & pi_ref = &iva; 具体的分析如下:

     1.不允许非const引用指向需要临时对象的对象或值 int a = 2; int &ref1 = a;// OK.有过渡变量。 const int &ref2 = 2;// OK.编译器产生临时变量,需要const引用

    2.地址值是不可寻址的值 int * const &ref3 = &a; // OK;

    3.于是,用const对象的地址来初始化一个指向指针的引用 const int b = 23; const int *p = &b; const int *& ref4 = p; const int *const & ref5 = &b; //OK const引用的语义到底是什么? 最后,我们可能仍然不明白const引用的这个const的语义是什么 const引用表示,试图通过此引用去(间接)改变其引用的对象的值时,编译器会报错! 这并意味着,此引用所引用的对象也因此变成const类型了。我们仍然可以改变其指向对象的值,只是不通过引用

    下面是一个简单的例子: 1 #include<iostream>

    2 using namespace std; 3 4 int main() 5 { 6 int val = 1024; 7 const int &ir = val; 8 9 val++; 10 //ir++; 11 12cout<<"val="<<val<<" "; 13cout<<"ir="<<ir; 14return0; 15 } 其中第10行,如果我们通过ir来改变val的值,编译时会出错。但是我们仍然可以通过val直接改变其值(第9行) 总结:const引用只是表明,保证不会通过此引用间接的改变被引用的对象! 另外,const既可以放到类型前又可以放到类型后面,放类型后比较容易理解: string const *t1; const string *t1; typedef string* pstring;string s; const pstring cstr1 = &s;就出错了 但是放在类型后面不会出错: pstring const cstr2 = &s; 使用编辑 引用在类中的使用

       1. #include<iostream> 2. using namespace std; 3. 4. class A 5. { 6. public: 7. A(int i=3):m_i(i){} 8. void print() 9. { 10. cout<<"m_i="<<m_i<<endl; 11. } 12. private: 13. int m_i; 14. }; 15. 16. class B 17. { 18. public: 19. B(){} 20. B(A& a):m_a(a){} 21. void display() 22. { 23. m_a.print(); 24. } 25. private: 26. A& m_a; 27. }; 28. 29. 30. int main(int argc,char** argv) 31. { 32. A a(5); 33. B b(a); 34. b.display(); 35. return0; 36. } 注意 引用在类中使用需注意  

     其中,要注意的地方就是引用类型的成员变量的初始化问题,它不能直接在构造函数里初始化,必须用到初始化列表,且形参也必须是引用类型。 凡是有引用类型的成员变量的类,不能有缺省构造函数。原因是引用类型的成员变量必须在类构造时进行初始化。 如果两个类要对第三个类的数据进行共享处理,可以考虑把第三个类作为这两个类的引用类型的成员变量

    了Point结构的默认构造函数: Point p = new Point(); 此调用后,该结构被认为已被明确赋值;也就是说该结构的所有成员均已初始化为各自的默认值。

    在写这两个变量类型之前,首先要理解另外两个概念:栈和堆

      那什么是栈和堆呢?

      计算机的内存从概念上分,会有许许多多的独立的块,栈和堆就是其中的两种内存块了。

      平时当我们调用一个方法时,假如这方法有参数的话,那么我们就需要为这个方法的参数跟方法所用到的变量分配内存。那么,

      参数跟变量的内存就是从栈中获得了,当这个方法结束的时候,这些参数跟变量所占用的内存就会自动释放回栈中。

      当我们使用new创建(实例化)一个对象(类)时,这时候就要分配到对象的内存,那么呢,这个对象的内存就是从堆中获得,当这个对象使用完成(异常也算)后,刚好相反,对象的内存是并不会释放回堆中的。怎么处理里?我们都知道.net有个垃圾回收机制的。 当然,我们编码的时候一些相于占资源的对象,一般都有我们自己分配释放。

      好了,理解那两个概念之后,再写值类型跟引用类型就容易理解多了

      值类型:

      值类型的变量本身就是含有赋予给它的数值的,它的变量本身及保存的数据都存储在栈的内存块当中,比如: intfloatbool这些类型,以及用struct定义的类型。

      当声明一个值类型时,必须对它初始化(给变量赋值)才能使用。否则,如int型,将边编译都编译不过(后补:字眼上的问题,当然,我的意思不是说这样编译不过一定就是值类型,这里打个比方也许不太恰当,因为大多都编译不过的)

     

      引用类型:

      即然值类型是存在栈的内存块当中,那么即然上面先提到堆,引用类型当然是分配到堆上的对象或者数据变量喽,根据官方一点的解释就是引用类型的变量只包括对其所表示的数据引用(如果对这句不太明白的话,看下以下的的例子)。

      打个比方,我们从类操作中很容易明白,多个引用类型变量(如:类变量)是可以引用同一个对象(类)的,所以,我们操作一个引用类型(如:类变量)时,有可能会影响到引用同一对象或者数据的其他变量了,就很好理解,引用类型的对象总是在进程堆中分配(动态分配)的。

      比如使用了关键字new初始化的数值类型变量的,因为当使用new对其初始化后,这个关键字就在堆为该变量分配了内存块,如下例中,变量_obj就引用了一个Sample类型的对象

     

       在所接触大多数.net(c#)开发人员的理解当中, 常常由于是用struct来定义(值类型)还是用class(引用类型)来定义的。当然,这虽然不是一个科学的办法,但也不失为了一个办法。  

  • 相关阅读:
    后端程序员之路 6、Python fabric
    后端程序员之路 5、.conf、libconfig
    后端程序员之路 4、一种monitor的做法
    后端程序员之路 3、fastcgi、fastcgi++
    后端程序员之路 2、nginx、php
    后端程序员之路 1、linux、centos
    F#之旅9
    F#之旅8
    F#之旅7
    F#之旅6
  • 原文地址:https://www.cnblogs.com/jakeasd/p/5491021.html
Copyright © 2020-2023  润新知