您的位置:首页 > 编程语言 > C语言/C++

C++语法笔记(二)

2013-10-04 17:21 148 查看
C++指针和引用
指针是某个变量在内存中的地址,T* 表示T类型变量的指针类型,其中T* p,p为指针类型的变量,p中可以存放一个T类型变量的地址。
引用是变量的别名,T& 表示T类型变量的别名,它和原变量在内存中占用一块地方,地址时钟相同,值始终相同。
区别:
指针可以为空,但引用不能为空(既然为别名,肯定要有原对象,不能为空)。故引用在定义的时候必须初始化(int& a = r).
引用不可以改变指向(但可改变所引用对象的内容),对一个对象"至死不渝";但是指针可以改变指向,而指向其它对象。说明:虽然引用不可以改变指向,但是可以改变初始化对象的内容。例如就++操作而言,对引用的操作直接反应到所指向的对象,而不是改变指向;而对指针的操作,会使指针指向下一个对象,而不是改变所指对象的内容。
引用的大小是所指向的变量的大小,因为引用只是一个别名而已;指针是指针本身的大小,一般4个字节。
引用比指针更安全。由于不存在空引用,并且引用一旦被初始化为指向一个对象,它就不能被改变为另一个对象的引用,因此引用很安全。对于指针来说,它可以随时指向别的对象,并且可以不被初始化,或为NULL,所以不安全。const 指针虽然不能改变指向,但仍然存在空指针,并且有可能产生野指针(即多个指针指向一块内存,free掉一个指针之后,别的指针就成了野指针)。
“sizeof 引用”得到的是所指向的变量(对象)的大小,而“sizeof 指针”得到的是指针本身的大小;
●指针和引用的自增(++)运算意义不一样;

Const
常量指针:
指向常量的指针,在指针定义语句的类型前加const,表示指向的对象是常量。定义指向常量的指针只限制指针的间接访问操作,而不能规定指针指向的值本身的操作规定性。即不可以通过*pointer改变指向的内容的值,但可以改变pointer 指向的变量地址。而且指针pointer指向的变量可以为const常量,也可以为变量从而变量可以自己改变。
int a = 0;
const
int*p;
p = &a;
a = 3。
常量引用:
指向常量的引用,在引用定义语句的类型前加const,表示指向的对象是常量。也跟指针一样不能利用引用对指向的变量进行重新赋值操作。
const int &d = a; (a可以为常量也可为变量)
指针常量:
在指针定义语句的指针名前加const,表示指针本身是常量。在定义指针常量时必须初始化!而这是引用天生具来的属性,不用再引用指针定义语句的引用名前加const。
指针常量定义"int*const pointer=&b"告诉编译器,pointer是常量,不能作为左值进行操作,但是允许修改间接访问值,即*pointer可以修改。
常量指针常量:指向常量的指针常量,可以定义一个指向常量的指针常量,它必须在定义时初始化。常量指针常量定义"constint* const pointer=&c"告诉编译器,pointer和*pointer都是常量,他们都不能作为左值进行操作。
不存在所谓的"引用常量",因为跟上面讲的一样引用变量就是引用常量。C++不区分变量的const引用和const变量的引用。程序决不能给引用本身重新赋值,使他指向另一个变量,因此引用总是const的。如果对引用应用关键字const,起作用就是使其目标称为const变量。即没有:Constdouble const& a=1;只有constdouble& a=1。
变量a:
int a = 0;
int *p =&a;
const
int*p = &a;
int *
constp = &a;

int &c = a;
const
int&c = a;
可以用常量指针、指针常量或变量指针指向a。
而常量a:
const
inta = 0;
const
int*p =&a;
const
int* const p = &a;

const
int&c = a;
只能用常量指针、指针常量指向a
总结:有一个规则可以很好的区分const是修饰指针,还是修饰指针指向的数据——画一条垂直穿过指针声明的星号(*),如果const出现在线的左边,指针指向的数据为常量;如果const出现在右边,指针本身为常量。而引用本身与天俱来就是常量,即不可以改变指向。
常量指针(指向常量的指针),指针常量(指针本身为常量)。

函数参数:形参、实参
主调函数与被调函数之间通过栈进行参数传递。因此被调函数对参数的访问是通过栈来访问的而访问的参数也是主调函数复制到栈上的。
形参出现在函数定义中,在整个函数体内都可以使用,离开该函数则不能使用。实参出现在主调函数中,进入被调函数后,实参变量也不能使用。形参和实参的功能是作数据传送。发生函数调用时,主调函数把实参的值传送给被调函数的形参从而实现主调函数向被调函数的数据传送。

1.形参变量只有在被调用时才分配内存单元,在调用结束时,即刻释放所分配的内存单元。因此,形参只有在函数内部有效。函数调用结束返回主调函数后则不能再使用该形参变量。 

2.实参可以是常量、变量、表达式、函数等,无论实参是何种类型的量,在进行函数调用时,它们都必须具有确定的值,以便把这些值传送给形参。因此应预先用赋值,输入等办法使实参获得确定值。 

3.实参和形参在数量上,类型上,顺序上应严格一致,否则会发生“类型不匹配”的错误。 

4.函数调用中发生的数据传送是单向的。即只能把实参的值传送给形参,而不能把形参的值反向地传送给实参。因此在函数调用过程中,形参的值发生改变,而实参中的值不会变化(只限于值传递)。

5.  当形参和实参不是指针类型时,在该函数运行时,形参和实参是不同的变量,他们在内存中位于不同的位置,形参将实参的内容复制一份,在该函数运行结束的时候形参被释放,而实参内容不会改变

如果函数的参数是指针类型变量,在调用该函数的过程中,传给函数的是实参的地址,在函数体内部使用的也是实参的地址,即使用的就是实参本身。所以在函数体内部可以改变实参的值。


函数参数和局部变量
把参数传递给函数有三种方法,一种是值传递,一种是传地址,还有一种是传引用。
(1)如果是值传递,当使用值传递的时候,会在函数里面生成传递参数的一个副本,这个副本的内容是按位从原始参数那里复制过来的,两者的内容是相同的。
和局部变量基本相同,一般而言都是在函数内可见(参数在函数内部改变不会传递到函数外部),参数作用是用来传递函数功能实现的输入数据的携带!
    (2)如果是地址传递的话,携带的是参数值地址而不是参数的表面值,这一般是参数为数组或指针的情况下的,有效范围不限于函数内部。通过地址值可以改变实参地址所指向的外部变量,但地址值的改变,外部实参(地址变量)不会发生变化。

(3)传引用不需要再函数内部生成副本,避免参数复制的开销。但有地址的复制。

但引用作为参数时,函数的形参作为局部变量,在栈中开辟内存,此时存放的为实参的地址!!当函数内部形参改变时,通过实参地址最终改变函数外部实参变量。

从概念上讲。指针从本质上讲就是存放变量地址的一个变量,在逻辑上是独立的,它可以被改变,包括其所指向的地址的改变和其指向的地址中所存放的数据的改变。
而引用是一个别名,它在逻辑上不是独立的,它的存在具有依附性,所以引用必须在一开始就被初始化,而且其引用的对象在其整个生命周期中是不能被改变的(自始至终只能依附于同一个变量)。

类的拷贝
当函数参数为一个c++类的对象(传值)时,需要在函数内部产生一个相同的副本,此时不会调用构造函数,因为类的构造函数是在对象初始化的时候设置对象的初始属性;在执行函数,传递对象引用进入函数时,对象的内部成员属性可能已经改变,故不能使用构造函数。
此时也不会调用“简单的复制、构造”(浅拷贝,即完成成员的一一复制),因为在函数执行完毕返回的时候,函数内部的对象的副本会执行析构函数,如果析构函数中含有对指针变量的delete,则会导致函数外部的原对象中的指针成员指向的内容被释放,从而出错。
此时,应该使用复制构造函数,而且传递的参数必须为引用, 如果不是引用会不断的调用复制构造函数(因为复制构造函数需要参数的副本,参数的副本产生靠复制构造函数),从而不断的新分配内存,导致溢出;而传引用不需要再在复制构造函数内部产生副本。则复制构造函数利用传递的对象引用,生成一个对象副本,于调用函数内部。
拷贝构造函数,新开辟一块内存区域,将原对象的非指针成员直接复制;对于指针成员,将原来对象的指针成员所指向的内存区域中的数据复制到新开辟的内存中。



赋值构造函数(拷贝构造函数)

调用拷贝构造函数的情形
在C++中,下面三种对象需要调用拷贝构造函数(有时也称“复制构造函数”):
1) 一个对象作为函数参数,以值传递的方式传入函数体;
2) 一个对象作为函数返回值,以值传递的方式从函数返回;
3) 一个对象用于给另外一个对象进行初始化(常称为复制初始化);
如果在前两种情况不使用拷贝构造函数的时候,就会导致一个指针指向已经被删除的内存空间。对于第三种情况来说,初始化和赋值的不同含义是拷贝构造函数调用的原因。事实上,拷贝构造函数是由普通构造函数和赋值操作符共同实现的。
通常的原则是:①对于凡是包含动态分配成员或包含指针成员的类都应该提供拷贝构造函数;②在提供拷贝构造函数的同时,还应该考虑重载"="赋值操作符号。
当我们没有显式定义一个复制构造函数时,编译器会隐式定义一个缺省的复制构造函数,它是一个内联的、公有的成员。

它具有下面的原型形式:

Point:: Point (const Point &pt)

{

xVal=pt. xVal;

yVal=pt. yVal;

}

可见,复制构造函数与构造函数的不同之处在于形参,前者的形参是Point对象的引用,其功能是将一个对象的每一个成员复制到另一个对象对应的成员当中。

缺省的复制构造函数也为浅拷贝,自己写的复制构造函数为深拷贝(其中解决了指针问题)

C++深拷贝与浅拷贝
当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝
深拷贝:需要自己重写复制构造函数(解决指针问题),若自己不重写复制构造函数,会出现指针问题。
class String

{

public:

String(); //构造函数

String(const String&s); //复制构造函数

~String(); //析构函数
// 接口函数

void set(char const *data);

char const *get(void);
private:

char *str; //数据成员ptr指向分配的字符串

};
String ::String(const String &s)

{

str = new char[strlen(s.str) + 1];

strcpy(str, s.str);

}











C++ 复制构造函数、赋值操作符、隐式类型转换

1. “=”表示的是初始化还是赋值关键在于等号左边对象是否已经存在(已经被构造)。如果不存在,则是初始化,应该调用构造函数或复制构造函数(具体视情况而定);如果存在,则是赋值,应该调用赋值操作符函数。


class A;

A a;

A b=a; //拷贝构造函数调用

//或

A b(a); //拷贝构造函数调用

///////////////////////////////////

A a;

A b;

b =a; //赋值运算符调用
在C++语言里,

String s2(s1);

String s3 = s1;

只是语法形式的不同,意义是一样的,都是定义加初始化,都调用拷贝构造函数。

若没有重载运算符“=”,则:

在A b = a,复制构造时,使用缺省的复制构造函数(浅拷贝),或重写的复制构造函数(深拷贝)

在 A b; b = a;时,只是执行简单复制(浅拷贝)。

2、拷贝构造函数首先是一个构造函数,它调用的时候产生一个对象,是通过参数传进来的那个对象来初始化,产生的对象。

operator=();是把一个对象赋值给一个原有的对象,所以如果原来的对象中有内存分配要先把内存释放掉,而且还要检查一下两个对象是不是同一个对象,如果是的话就不做任何操作。
CExample& CExample::operator = (constCExample& RightSides)
{
if (this == &RightSides)//如果自己给自己赋值则直接返回
return *this;
nSize=RightSides.nSize;//复制常规成员
char*temp=newchar[nSize];//复制指针指向的内容
memcpy(temp,RightSides.pBuffer,nSize*sizeof(char));
delete []pBuffer;//删除原指针指向内容(将删除操作放在后面,避免X=X特殊情况下,内容的丢失)
pBuffer=temp;//建立新指向
return *this;
}

2. 隐式类类型转换:用单个实参来调用的构造函数定义了从形参类型到类类型的一个隐式转换(C++ Primer)。如果是多个实参,则无法完成隐式转换。可以用关键字"explicit"声明函数防止这种隐式转换。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: