XSLT存档  

不及格的程序员-八神

 查看分类:  ASP.NET XML/XSLT JavaScripT   我的MSN空间Blog

浅谈C++三种传参方式 

 

浅谈C++三种传参方式

C++给函数传参中,主要有三种方式:分别是值传递、指针传递和引用传递。

下面通过讲解和实例来说明三种方式的区别。

值传递

我们都知道,在函数定义括号中的参数是形参,是给函数内专用的局部变量,意味着函数接收到的是实参的副本,如果形参的值在函数内部被改变,对实参是没有影响的

#include <iostream>

using namespace std;

void change(int formalNum) {
	formalNum = 0;
	cout << "formalNum address: " << &formalNum << endl;
}

int main() {
	int realNum = 10;
	cout << "Before Change: " << realNum << endl;
	cout << "realNum address: " << &realNum << endl;
	change(realNum);
	cout << "After Change: " << realNum ;
	return 0;
}

// 执行结果
Before Change: 10
realNum address: 008FFDA0
formalNum address: 008FFCCC
After Change: 10

可以看见,实参和形参的地址完全不一样,而且函数完全没有办法改变实参的值。值传递的作用更多是让函数内部了解外部参数的值。值传递是单向的,只能由实参传向形参。

指针传递

指针传递很好理解,形参为指向实参地址的指针,当对形参操作时,等同于直接通过地址操作实参。

#include <iostream>

using namespace std;

void change(int *ptr) {
	*ptr = 0;
}

int main() {
	int realNum = 10;
	int* ptr = &realNum;
	cout << "Before Change: " << realNum << endl;
	change(ptr);
	cout << "After Change: " << realNum ;
	return 0;
}

// 执行结果
Before Change: 10
After Change: 0

可以很明显地看见,我们在函数内部成功地修改了实参的值。是C++很常见的一种传参方式。

引用传递

引用传递其实是最难理解的一种传参方式。在详细剖析它之前,我们先说他的功能。

向函数传递参数的引用调用方法,把引用的地址复制给形式参数。在函数内,该引用用于访问调用中要用到的实际参数。这意味着,修改形式参数会影响实际参数。

那么肯定有人问了,既然都是直接影响,指针和引用有啥区别呢???那区别可大了去了。

  1. 指针从本质上是一个变量,是一个整形变量,存放的是另一个变量的地址。指针在逻辑上是独立的,它可以被改变,甚至能改变它的值(指向其他地址),并且可以取出对应内存中的数据。
  2. 引用可以理解为外号,是另一个变量的同义词,它在逻辑上具有依附性,所以C++也规定引用的在创立的时候就必须被初始化(现有一个变量,然后创建对该变量的引用)。而且其引用的对象在其整个生命周期中不能被改变,即自始至终只能依附于同一个变量(初始化的时候代表的是谁的别名,就一直是谁的别名,不能变)。
  3. 在引用传递过程中,被调函数的形式参数虽然也作为局部变量在栈中开辟了内存空间,但是这时存放的是由主调函数放进来的实参变量的地址。被调函数对形参的任何操作都被处理成间接寻址,即通过栈中存放的地址访问主调函数中的实参变量。正因为如此,被调函数对形参做的任何操作都影响了主调函数中的实参变量。

引用的规则:

  • 引用被创建的同时必须被初始化(指针则可以在任何时候被初始化)。
  • 不能有NULL引用,引用必须与合法的存储单元关联(指针可以有野指针,可以指向NULL)。
  • 一旦引用被初始化,就不能改变引用的关系(指针则可以随时改变所指的对象)。

看了这么多,指针传递引用传递的用处是什么呢?

  1. 函数内部修改参数并且希望改动影响调用函数。对比指针/引用传递可以将改变由形参“传给”实参(实际上就是直接在实参的内存上修改);
  2. 当一个函数实际需要返回多个值,而只能显式返回一个值时,可以将另外需要返回的变量以指针/引用传递。

下面看具体操作:

引用变量的定义方法和常规变量类似,但是其数据类型和名称之间有一个 & 符号。例如,以下函数定义使形参 refNum 成为引用变量:

#include <iostream>

using namespace std;

void change(int& refNum) {
	refNum = 0;
	cout << "reference address: " << &refNum << endl;
}

int main() {
	int realNum = 10;
	cout << "Before Change: " << realNum << endl;
	cout << "realNum address: " << &realNum << endl;
	change(realNum);
	cout << "After Change: " << realNum ;
	return 0;
}

// 执行结果
Before Change: 10
realNum address: 00A4F9F4
reference address: 00A4F9F4
After Change: 0

可以看见,引用传递成功地改变了参数的值,同时形参的地址和实参的地址其实是一模一样的

在学完数据结构和算法后,我对其又有新的认识。

void func(nodeList* &Node){
    // 这里对Node进行了操作
}

上面这个传参,又有*又有&,第一眼有点懵,后来细想一下其实很简单。

nodeList是一个整体,代表传进来的是nodeList这个类的指针。我们之前已经学到了,指针其实是一个变量,它的基本性质和变量没有区别。那么我们要在函数体内改变其值,最安全的办法就是传入其引用(也可以创建指针的指针)。所以这里的&Node表示引用Node的实参,是Node的别名,操作引用变量就相当于操作实参变量。*


CEx08aApp::CEx08aApp()
{
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
    int a =0;
    int b =1;
    testEBP(a,a, b,b);

68:       int a =0;
00402BBC C7 45 EC 00 00 00 00 mov         dword ptr [ebp-14h],0
69:       int b =1;
00402BC3 C7 45 E8 01 00 00 00 mov         dword ptr [ebp-18h],1
70:       testEBP(a,a, b,b);
00402BCA 8B 4D E8             mov         ecx,dword ptr [ebp-18h]
00402BCD 51                   push        ecx
00402BCE 8B 55 E8             mov         edx,dword ptr [ebp-18h]
00402BD1 52                   push        edx
00402BD2 8B 45 EC             mov         eax,dword ptr [ebp-14h]
00402BD5 50                   push        eax
00402BD6 8B 4D EC             mov         ecx,dword ptr [ebp-14h]
00402BD9 51                   push        ecx //copy
00402BDA 8B 4D F0             mov         ecx,dword ptr [ebp-10h]
00402BDD E8 EE 00 00 00       call        CEx08aApp::testEBP (00402cd0)


--- F:\C++资料\yangl_test\ex08a\ex08a.cpp  ----------------------------------------------------------------------------------------
92:
93:   int CEx08aApp::testEBP(int a, int b, int c, int d) //未写类前缀
94:   {
00402CD0 55                   push        ebp
00402CD1 8B EC                mov         ebp,esp
00402CD3 83 EC 48             sub         esp,48h
00402CD6 53                   push        ebx
00402CD7 56                   push        esi
00402CD8 57                   push        edi
00402CD9 51                   push        ecx
00402CDA 8D 7D B8             lea         edi,[ebp-48h]
00402CDD B9 12 00 00 00       mov         ecx,12h
00402CE2 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00402CE7 F3 AB                rep stos    dword ptr [edi]
00402CE9 59                   pop         ecx
00402CEA 89 4D FC             mov         dword ptr [ebp-4],ecx
95:       int r = a+b+c+d;
00402CED 8B 45 08             mov         eax,dword ptr [ebp+8]
00402CF0 03 45 0C             add         eax,dword ptr [ebp+0Ch]
00402CF3 03 45 10             add         eax,dword ptr [ebp+10h]
00402CF6 03 45 14             add         eax,dword ptr [ebp+14h]
00402CF9 89 45 F8             mov         dword ptr [ebp-8],eax
96:       d = 0;
00402CFC C7 45 14 00 00 00 00 mov         dword ptr [ebp+14h],0 //修改的本地栈内存
97:       return 0;
00402D03 33 C0                xor         eax,eax
98:   }
00402D05 5F                   pop         edi
00402D06 5E                   pop         esi
00402D07 5B                   pop         ebx
00402D08 8B E5                mov         esp,ebp
00402D0A 5D                   pop         ebp
00402D0B C2 10 00             ret         10h

类对象的拷贝构造函数:

testClass::testClass(const testClass & {...}) address 0x00402d20
CEx08aApp::CEx08aApp() line 71 + 23 bytes

CEx08aApp::CEx08aApp()
{
    // TODO: add construction code here,
    // Place all significant initialization in InitInstance
    testClass b;
    testEBP(b,1,2,3);
================================
70:       testClass b;
00402BBC 8D 4D D8             lea         ecx,[ebp-28h]
00402BBF E8 5C 00 00 00       call        testClass::testClass (00402c20)
00402BC4 C6 45 FC 01          mov         byte ptr [ebp-4],1
71:       testEBP(b,1,2,3);
00402BC8 6A 03                push        3
00402BCA 6A 02                push        2
00402BCC 6A 01                push        1
00402BCE 83 EC 18             sub         esp,18h
00402BD1 8B CC                mov         ecx,esp
00402BD3 89 65 D4             mov         dword ptr [ebp-2Ch],esp
00402BD6 8D 55 D8             lea         edx,[ebp-28h]
00402BD9 52                   push        edx
00402BDA E8 41 01 00 00       call        testClass::testClass (00402d20) 拷贝构造函数
00402BDF 89 45 D0             mov         dword ptr [ebp-30h],eax
00402BE2 8B 4D F0             mov         ecx,dword ptr [ebp-10h]
00402BE5 E8 16 02 00 00       call        CEx08aApp::testEBP (00402e00)
==================================

testClass::testClass:
00402D20 55                   push        ebp
00402D21 8B EC                mov         ebp,esp
00402D23 83 EC 44             sub         esp,44h
00402D26 53                   push        ebx
00402D27 56                   push        esi
00402D28 57                   push        edi
00402D29 51                   push        ecx
00402D2A 8D 7D BC             lea         edi,[ebp-44h]
00402D2D B9 11 00 00 00       mov         ecx,11h
00402D32 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00402D37 F3 AB                rep stos    dword ptr [edi]
00402D39 59                   pop         ecx
00402D3A 89 4D FC             mov         dword ptr [ebp-4],ecx
00402D3D 8B 45 FC             mov         eax,dword ptr [ebp-4]
00402D40 8B 4D 08             mov         ecx,dword ptr [ebp+8]
00402D43 8B 11                mov         edx,dword ptr [ecx]
00402D45 89 10                mov         dword ptr [eax],edx
00402D47 8B 45 FC             mov         eax,dword ptr [ebp-4]
00402D4A 8B 4D 08             mov         ecx,dword ptr [ebp+8]
00402D4D 8B 51 04             mov         edx,dword ptr [ecx+4]
00402D50 89 50 04             mov         dword ptr [eax+4],edx
00402D53 8B 45 08             mov         eax,dword ptr [ebp+8]
00402D56 83 C0 08             add         eax,8
00402D59 8B F4                mov         esi,esp
00402D5B 50                   push        eax
00402D5C 8B 4D FC             mov         ecx,dword ptr [ebp-4]
00402D5F 83 C1 08             add         ecx,8
00402D62 FF 15 6C BA 40 00    call        dword ptr [__imp_??0?$basic_string@DU?$char_traits@D@std@@V?$allocator@D@2@@std@@QAE@ABV
00402D68 3B F4                cmp         esi,esp
00402D6A E8 25 3D 00 00       call        _chkesp (00406a94)
00402D6F 8B 45 FC             mov         eax,dword ptr [ebp-4]
00402D72 5F                   pop         edi
00402D73 5E                   pop         esi
00402D74 5B                   pop         ebx
00402D75 83 C4 44             add         esp,44h
00402D78 3B EC                cmp         ebp,esp
00402D7A E8 15 3D 00 00       call        _chkesp (00406a94)
00402D7F 8B E5                mov         esp,ebp
00402D81 5D                   pop         ebp
00402D82 C2 04 00             ret         4
==============================

94:   int CEx08aApp::testEBP(testClass a, int b, int c, int d) //δдÀàǰ׺
95:   {
00402E00 55                   push        ebp
00402E01 8B EC                mov         ebp,esp
00402E03 83 EC 48             sub         esp,48h
00402E06 53                   push        ebx
00402E07 56                   push        esi
00402E08 57                   push        edi
00402E09 51                   push        ecx
00402E0A 8D 7D B8             lea         edi,[ebp-48h]
00402E0D B9 12 00 00 00       mov         ecx,12h
00402E12 B8 CC CC CC CC       mov         eax,0CCCCCCCCh
00402E17 F3 AB                rep stos    dword ptr [edi]
00402E19 59                   pop         ecx
00402E1A 89 4D FC             mov         dword ptr [ebp-4],ecx
96:       a.fieldA = 1;
00402E1D C7 45 08 01 00 00 00 mov         dword ptr [ebp+8],1
97:       return 0;
00402E24 C7 45 F8 00 00 00 00 mov         dword ptr [ebp-8],0
00402E2B 8D 4D 08             lea         ecx,[ebp+8]
00402E2E E8 9D FE FF FF       call        testClass::~testClass (00402cd0)
00402E33 8B 45 F8             mov         eax,dword ptr [ebp-8]
98:   }
00402E36 5F                   pop         edi
00402E37 5E                   pop         esi
00402E38 5B                   pop         ebx
00402E39 83 C4 48             add         esp,48h
00402E3C 3B EC                cmp         ebp,esp
00402E3E E8 51 3C 00 00       call        _chkesp (00406a94)
00402E43 8B E5                mov         esp,ebp
00402E45 5D                   pop         ebp
00402E46 C2 24 00             ret         24h

       

 

 


 

拷贝构造函数何时调用?

 
class bulk_item{
public:
    bulk_item(int val):x(val) {}
    bulk_item(const bulk_item & rhs)
    {
        cout << "!!!" << endl; 
        x = rhs.x;
    }
private:
    //bulk_item(const bulk_item &);
    int x;
};

int main(int argc, char const *argv[])
{
    bulk_item a = bulk_item(10);
    return 0;
}

c++ primer 上明确说在复制初始化时,会调用拷贝构造函数,但是我上面这段代码执行的时候没有打印,说明没有去执行我自定义的拷贝构造函数。不解。。。 
 

背景知识

之前写的几篇文章就派上用场了,在接着往下阅读之前,我们需要首先掌握以下知识:

正文部分

典型案例

/*虚构有这么一个类,如有雷同,纯属巧合*/
class Student
{
public:
    Student(const char* name, int size)
    {
        _name = new char[size];
        strncpy_s(_name, size, name, MaxNameSize);
    }
    ~Student()
    {
        delete _name;
        _name = nullptr;
    }
private:
    char* _name;
    static constexpr int MaxNameSize = 64;
};

// 实际工程中用值类型传递一个对象
// 也是存在的,很有可能还不在少数
static void Save(Student s)
{

}

int main()
{
    constexpr char Jim[] {"Jim"};
    constexpr size_t SizeOfJim = sizeof (Jim) / sizeof (char);
    Student jim{Jim, SizeOfJim};
    Save(jim);

    return 0;
}

我们先分析下:

  1. Student没有提供拷贝构造函数
  2. Save(Student s) 函数中的形参是个值类型

因为Student没提供拷贝构造函数,而又有Save(Student s)这样的函数要用,所以编译器就会助人为乐给你一个,但编译器又不知道Student类中存在像char* _name;这样的需要在堆上分配内存的成员变量,这样就会导致在调用Save(jim);这个函数的时候,复制出来的临时对象s和作为实参传进来的jim对象指向同一块堆内存_name。注:这里比较拗口,不妨多读几遍。

这样可能会因为对象生命期的不一致或者处于不同的线程,从而使得程序访问已经被释放掉的_name引发程序崩溃。

要解决这个问题,一种办法是提供一个拷贝构造函数,管理好堆内存,这样不管Save函数的参数是值类型的还是引用类型的,至少不会引发程序崩溃。

另一种办法就是我们要着重讨论的:抑制类的拷贝构造,通常情况下,在程序员不能明确表达自己的设计意图的时候,笔者是推荐这么做的。

抑制类的拷贝构造

  • 只声明不实现法,C++11之前流行的做法
class Student
{
public:
    Student(const char* name, int size)
    {
        _name = new char[size];
        strncpy_s(_name, size, name, MaxNameSize);
    }
    ~Student()
    {
        delete _name;
        _name = nullptr;
    }

private:
    /* 看这里,只声明不实现 */
    /* 
     *  至于访问权限,不推荐用public,
     *  protected还是private的取决于实际场景
     */
    Student(const Student&);

private:
    char* _name;
    static constexpr int MaxNameSize = 64;
};
  • 使用delete告诉编译器别没事找事干,C++11之后流行的做法
class Student
{
public:
    Student(const char* name, int size)
    {
        _name = new char[size];
        strncpy_s(_name, size, name, MaxNameSize);
    }

    /* 看这里,直接delete掉 */
    /* 这里的访问权限为public可以明确你的设计意图 */
    Student(const Student&) = delete;

    ~Student()
    {
        delete _name;
        _name = nullptr;
    }
private:
    char* _name;
    static constexpr int MaxNameSize = 64;
};

别忘了抑制赋值函数

int main()
{
    constexpr char Jim[] {"Jim"};
    constexpr size_t SizeOfJim = sizeof (Jim) / sizeof (char);
    Student jim{Jim, SizeOfJim};

    constexpr char Mike[] {"Mike"};
    constexpr size_t SizeOfMike = sizeof (Mike) / sizeof (char);
    Student mike{Mike, SizeOfMike};

    /* 这样编译能通过吗? */
    mike = jim;
}

形如上面的mike = jim;是要发生灾难的,同样因为编译器会乐于助人地提供了一个默认赋值函数,所以同时也要抑制赋值函数,风格尽量保持跟抑制拷贝构造函数的方式一致。

总结

通过明确是否抑制构造函数和赋值函数,清晰地表达出设计意图,渐渐地提升我们的设计思维。这里是比较常见的抑制手法,对于非常熟悉C++类的机制的开发人员来说我们可以这么做,但当团队中存在人员流动并且类的数目和变动比较频繁的情况下,这种做法显得有点单薄了,难以形成体系。有没有其他的方案呢?

因为这篇篇幅有点长了,知友们可能已经累了,找时间再进一步讨论。

清华大学 c++语言程序设计

拷贝构造函数 在以下三种情况下会被调用:

当用类的一个对象初始化该类的另一个对象时

int main(void)
{
    Point A(1,2);
    Point B(A);  
}

如果函数形参是类的对象,调用函数时,进行形参和实参结合时, void fun(Point p);

如果函数的返回值是类的对象,函数执行完成返回调用者时

Point getP()
{
    Point A(1,2);
    return A;
}

int main(void)
{
  Point B = getP();  
}

 

 

 

 

 

 

 

 

 

 

 
posted on 2023-06-08 14:07  不及格的程序员-八神  阅读(19)  评论(0编辑  收藏  举报