1500字范文,内容丰富有趣,写作好帮手!
1500字范文 > 对象传值到函数的三种类型:传值 指针 引用

对象传值到函数的三种类型:传值 指针 引用

时间:2019-12-03 06:54:47

相关推荐

对象传值到函数的三种类型:传值 指针 引用

最近再次拿起C++的书复习,然后觉得自己欠缺很多,我始终相信好记性不如烂笔头,所以我再次下定决心要把自己理解的写成文字,以后可以回过来看看。好了,废话不多说,这是我这段时间第一篇文章,鼓励下自己(o(∩_∩)o )!

我是根据自己的能力写的,有不足和错误的地方请大家指正,并最好通过邮箱或在本博客留言告诉我,我会非常感谢。

对象传值到函数:传值、传递指针、引用。

1. 传值

内置对象,如:int、float、double等。这些对象的传值没什么特别。

非内置对象,如:类对象等。这些对象的传值就稍微复杂点,至少比内置对象要复杂很多。

先来看个简单的例子,

#include <iostream>using namespace std;class A{public: void print() { cout<<"Class A!"<<endl; } };void test1(A a){ a.print(); }int main(int argc, char *argv[]){ A a; test1(a); return 0;}//运行结果:Class A!

嗯,确实这个代码很简单,也很普通。如果我这里代码修改下:

按 Ctrl+C 复制代码

按 Ctrl+C 复制代码

编译也是正常的,结果也是一样。我传进去的确是一个整型,你没看错。这里涉及到一个知识点,通过类的单参数构造函数进行隐式类型转换。显示的转换就是这样:

test1(A(a));

当然这个在需要的时候是很好,但是在某些情况下却是一种麻烦了,例如:

class String {

String ( int n ); //本意是预先分配n个字节给字符串

String ( const char* p ); // 用C风格的字符串p作为初始化值

//…

}

下面两种写法比较正常:

String s2 ( 10 ); //OK 分配10个字节的空字符串

String s3 = String ( 10 ); //OK 分配10个字节的空字符串

下面两种写法就比较疑惑了:

String s4 = 10; //编译通过,也是分配10个字节的空字符串

String s5 = ‘a’; //编译通过,分配int(‘a’)个字节的空字符串

s4 和s5 分别把一个int型和char型,隐式转换成了分配若干字节的空字符串,容易令人误解。

为了避免这种错误的发生,我们可以声明显示的转换,使用explicit关键字:

class String {

explicitString ( int n );//本意是预先分配n个字节给字符串

String ( const char* p );//用C风格的字符串p作为初始化值

//…

}

加上explicit,就抑制了String ( int n )的隐式转换,

下面两种写法仍然正确:

String s2 ( 10 ); //OK分配10个字节的空字符串

String s3 = String ( 10 );//OK分配10个字节的空字符串

下面两种写法就不允许了:

String s4 = 10;//编译不通过,不允许隐式的转换

String s5 = ‘a’;//编译不通过,不允许隐式的转换

因此,某些时候,explicit可以有效得防止构造函数的隐式转换带来的错误或者误解。

总结:explicit 只对构造函数起作用,用来抑制隐式转换。

o(∩_∩)o 很好,又学会一个关键字explicit的作用。

除了使用explicit还有其他方法解决的(自己思考或参考More Effecitve C++这本书),但是目前我理解当中,使用explicit是最简单的。

还有个问题是这样,如果函数参数类型是基类,传进去的值是其派生类的对象,那么会怎么样。那它的派生类行为就被切掉了(sliced off)。这样的a(这个a是上面代码test1函数里的参数)对象实际上是一个基类对象:它没有派生类的数据成员,而且当本准备调用它们的虚拟函数时,系统解析后调用的却是基类对象的函数。

看实例理解:

#include <iostream>using namespace std;class A{public: virtual void print() { cout<<"Class A!"<<endl; } };class B : public A{public: void print() { cout<<"Class B!"<<endl; } void print2() { cout<<"Class B2!"<<endl; }};void test1(A a){ a.print(); //a.print2(); 编译错误 }int main(int argc, char *argv[]){ B b; test1(b); return 0;}//运行结果:Class A!

俄,到这里已经觉得一个函数传值有这么多名堂呢?其实我这里的也只是其中一部分我觉得,如果还有其他要注意的请留言啊,不要自己藏着拿出来分享。

2. 传递指针

#include <iostream>using namespace std;class A{public: virtual void print() { cout<<"Class A!"<<endl; }};class B : public A{public: void print() { cout<<"Class B!"<<endl; } void print2() { cout<<"Class B2!"<<endl; }};void test1(A *a){ a->print(); //a->print2(); //编译错误 }int main(int argc, char *argv[]){ B b; test1(&b); return 0;}//运行结果:Class B!

嗯?运行结果不同了。嗯,是的。到这里可以思考了。上面参数值传递是对象拷贝的,拷贝的时候基类的结构和派生类不一样,基类的空间结构小于派生类,派生类对象拷贝到基类那么必然切掉了一些,切掉可想而知是派生类新定义的成员。而指针只是指向原来那片空间,尽管指针类型是基类,基类是可以向派生类转换的隐式。我的理解是这样。故指针还是保留着派生类所有成员。

3. 引用

同样的,将指针的修改成引用。

#include <iostream>using namespace std;class A{public: virtual void print() { cout<<"Class A!"<<endl; }};class B : public A{public: void print() { cout<<"Class B!"<<endl; } void print2() { cout<<"Class B2!"<<endl; }};void test1(A &a){ a.print(); //a->print2(); //编译错误 }int main(int argc, char *argv[]){ B b; test1(b); return 0;}//运行结果:Class B!

嗯,效果和指针一样,但是有点不同,它不像指针那样临时创建个新的指针,它的参数名称就是该对象的别名,其实就是这个对象,只是取了个小名。就是有个孩子叫毛阿敏,小名叫小毛,其实就是同一个人。指针传递就是去北京最出名的烤鸭掉,只要知道了地址,那么谁都可以到达吃到香喷喷的烤鸭,不管他是阿三还是阿四,就像指针P1或P2,只要得到了这个地址。

还有指针和引用的区别:

指针操作比较繁琐,不注意就会出错。

如:

指针需要->,而引用就像平常的一样使用就行了。

一般定义指针变量都要赋值,防止程序出错。指针不赋初值,编译器也是编译正常,不会告诉你,需要赋值更加好。而引用的话,不赋初值会编译错误,在一定程度上提高程序的正确性和安全性。如:

char *p; //可以不赋值

char &p;//编译错误

char *p1=0; //编译正确

char &p2=(*p1);//编译正确

同时在函数参数传递的时候也是这样,可以确保传进来的不是空值。

总结:内置对象传值很简单,非内置的话要注意单参数构造函数的隐式转换,基类与派生类在传递的时候注意要使用引用或指针,个人感觉引用较好。还有就是指针和引用最主要的区别就是指针指向数据内存空间,而引用则是就是它本身只是取了个别名。

好了,我要总结的就这些了,请大家指正。

参考文献:

[1]Scott Meyers.More Effecitve C++.

[2]/cutepig/archive//01/14/1375917.html

[3]/foxlee/archive//08/02/205566.html

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。