C++传值和传引用

传值参数

首先你肯定明白一个道理:当初始化一个非引用类型的变量时,初始值被拷贝给变量,此时对变量的改动不会涌向初始值

int n = 0;
int i = 1;  // i是n的副本
i = 42;     // i的值改变,n的值不改变

传值参数的机理完全一样,由于每次调用函数时都会重新创建它的形参,并用传入的实参对形参进行初始化,所以函数对形参做的所有操作不会影响实参,如果我们想让函数改变我们传入的实参本身们就可以用到指针形参访问函数外部对象

指针形参

先看一段代码:

int n = 0;
int i = 1;
int *p1 = &n; //p1指向n
int *p2 = &i; //p2指向i
*p1 = 42;     //n的值改变,p1不变
p1 = p2       //现在p1指向了i,但是i和n的值都不变

当执行指针拷贝操作时,拷贝的是指针的值(地址),拷贝之后,两个指针是不同的指针,因为指针可以使我们间接地访问所指向的对象,所以通过指针可以修改对象的值

指针形参也差不都

// 接受一个int类型的指针,然后将指针所指向的对象置0
void reset(int* ip)
{
    *ip = 0; // 改变指针ip所指向对象的值
    ip = 0;  // 只改变了ip的局部拷贝,实参未被改变
}

void reset(int i)
{
    i = 0; 
}

int main()
{
    int i = 42;
    reset(i);                   // 改变i的值而非i的地址
    cout << "i = " << i << endl;
    reset(&i);                  // 改变i的值而非i的地址
    cout << "i = " << i << endl;
}

调用这个reset函数之后,实参所指向的对象被置为0,但是实参本身并没有改变,输出如下

i = 42

i = 0

熟悉C语言的程序员可能经常使用指针形参访问函数外部的对象,但在C++中建议使用引用类型的形参代替指针

传引用参数

我们知道对于引用的操作实际上是作用在所引用的对象上的:

int n = 0;
int i = 1;
int &r = n; // r绑定了n
r = 42;     // 现在n=42
r = i;      // 现在n的值和i相同=1
i = r;      // i的值和n相同

引用形参与之类似,通过引用形参,允许函数改变实参的值

// 接受一个int对象的引用,然后将对象的值置为0
void reset(int& i)
{
    i = 0; //改变了i所引对象的值
}

当调用这一版本的reset时,直接传入对象而无须传递地址,被改变的对象是传入reset的实参

void reset(int& j)
{
    j = 0; //改变了i所引对象的值
}

int main()
{
    int j = 42;
    reset(j);
    cout << "j = " << j << endl;
}

输出:

j = 0

避免拷贝

举个例子,我们准备编写一个函数比较两个string对象的长度,对象可能会非常长,所以应该尽量避免直接拷贝它们,这时候引用形参就可以避免拷贝

因为无需改变对象内容,所以把形参定义为对常量的引用

bool isShoter(const string &s1, const string &s2)
{
    return s1.size() < s2.size();
}

const形参和实参

先简单回顾下const

const int ci = 42;  //顶层const,不能改变ci
int i = ci;         //√ 拷贝时候忽略了ci的顶层
int* const p = &i;  //顶层const,不能给p赋值(改变p指向的对象)
*p = 0;             //√ 可以通过p改变对象内容,i = 0

关于const的详解:https://www.cnblogs.com/zhxmdefj/p/11524403.html

一样,形参初始化时会忽略掉顶层const

void fucn(const int i) {
    //fucn能读i,但不能向i写值
}
//调用fucn时,既可以传入const int,也可以传入int

但因此也要注意一个问题

void fucn(const int i) {
    //fucn能读i,但不能向i写值
}

void fucn(int i) {}

因为顶层const被忽略掉了,所以上面两个fucn的参数可以完全一样,因此第二个fucn会报错

指针或引用形参与const

形参初始化方式和变量初始化方式是一样的,先回顾下初始化规则:

int i = 42;
const int* cp = &i; //√ cp不能改变i
const int& r = i;   //√ r能改变i
const int& r2 = 42; //√ 
int* p = cp;        //× p的类型和cp不匹配
int& r3 = r;        //× r3的类型和r不匹配
int& r4 = 42;       //× 不能用字面值初始化非常量引用

具体解析:https://www.cnblogs.com/zhxmdefj/p/11524403.html

将同样的规则应用到参数传递:

void reset(int* ip)
{
    *ip = 0; // 改变指针ip所指向对象的值
    ip = 0;  // 只改变了ip的局部拷贝,实参未被改变
}

void reset(int& j)
{
    j = 0; //改变了i所引对象的值
}

int main()
{
    int i = 0;
    const int ci = i;
    string::size_type ctr = 0;
    reset(&i);      //√ 调用void reset(int* ip)
    reset(&ci);     //× 不能用指向const int对象的指针初始化int*
    reset(i);       //√ 调用void reset(int& j)
    reset(ci);      //× 不能把普通引用绑定到const对象ci上
    reset(42);      //× 不能把普通引用绑定到字面值上
    reset(ctr);     //× ctr是无符号类型,类型不匹配
}

尽量使用常量引用

把函数不会改变的形参定义成普通引用,往往会给函数调用者一误导:函数可以修改它实参的值

同时还会极大地限制函数所能接受的实参类型(比如上面的reset不能接受const)

数组形参

数组的两个特性:1.不允许拷贝数组 2.使用数组时会将其转换为指针

因此,我们不能以值传递的方式使用数组,并且传递数组时,实际上传递的是指向首元素的指针

//等价
void print(const int*);
void print(const int[]);
void print(const int[10]);

上面三个函数是等价的,每个函数的唯一形参都是const int*类型

void print(const int*);

int main()
{
    int i = 0;
    int j[2] = { 0,1 };
    print(&i);      //√ &i的类型是int*
    print(j);       //√ j转换成int*指向j[0]
}

管理数组实参

使用标记指定数组长度

(C风格字符串存储在字符数组,最后一个字符后面跟着一个空字符)

//输出C风格字符串
void print(const char* cp) {
    if (cp)                 //若cp不是一个空指针
        while (*cp)         //只要指针所指的字符不是空字符
            cout << *cp++;  //输出当前字符,并向后移动一个位置
}

这种方法适用于有明显结束标记的情况

使用标准库规范

传递指向数组首元素和尾后元素的指针

//输出beg到end之间(不含end)所有元素
void print(const int* beg, const int* end) {
    while (beg != end)
        cout << *beg++ << endl; //输出当前元素,并向后移动一个位置
}

显示传递一个表示数组大小的实参

C程序和古老的C++程序员常常使用这种技法

void print(const int* ia, size_t size) {
    for (size_t i = 0; i != size; ++i)
        cout << ia[i] << endl;
}

P.S 关于数组形参和const:当函数不需要对数组元素执行写操作时,数组形参应该是指向const的指针

数组引用形参

C++允许将变量定义成数组的引用,所以形参也可以是数组的引用

void print(int(&arr)[10]) {
    for (auto elem : arr)
        cout << elem << endl;
}

注意(&arr)两端括号必不可少

f(int &arr[10])     //arr是有10个整形引用的数组
f(int (&arr)[10])   //arr是有10个整数的整形数组的引用

传递多维数组

多维数组其实就是数组的数组,所以首元素本身就是一个数组,阵阵就是一个指向数组的指针

//martrix指向数组的首元素,该数组的元素是由10个整数构成的
void print(int(*martrix)[10], int rowSize) {
}

(*martrix)两端括号必不可少

//等价定义
void print(int martrix[][10], int rowSize) {
}
posted @ 2019-09-18 14:17 zhxmdefj 阅读(...) 评论(...) 编辑 收藏