笔者定义了一个结构体变量,用于作为函数的形参,定义如下:
struct CardParameters {float* Average = nullptr;int averageSize = 0;
};
需求描述:结构体变量作为函数的形参,在函数体中给指针变量分配内存空间并赋值,在函数调用结束后,在外部可以访问这些数据,用于其他用途。
情形1:结构体作为函数形参时,值传递
函数的声明:
void collectData(CardParameters card); // 值传递,不会修改原对象
函数的调用:
CardParameters CardSet;
CardSet.averageSize = 10;collectData(CardSet);
函数体示例:
void collectData(CardParameters card) {card.Average = new float[10]; card.averageSize = 10;for (int i = 0; i < card.averageSize; ++i) {card.Average[i] = static_cast<float>(i);}
}
值传递时,函数内部操作的是结构体的一个拷贝(copy),你在函数里对 card.Average
的赋值只影响这个拷贝,不会影响到外面的原始结构体 CardSet
。函数结束后,拷贝被销毁,里面的 Average
指针也随之失效,而外部的 CardSet.Average
还是原来的值。
在函数调用语句结束后,笔者试图访问结构体中的成员变量,发现CardSet.Average = nullptr,CardSet.averageSize = 0,也就是说结构体中变量的值还是初值,并没有任何改变。
情形2:结构体作为函数形参时,引用传递
函数的声明:
void collectData(CardParameters &card); // 引用传递,会修改原对象
函数体示例:
void collectData(CardParameters &card) { // 注意这里的 &card.averageSize = 10;card.Average = new float[card.averageSize];for (int i = 0; i < card.averageSize; ++i) {card.Average[i] = static_cast<float>(i);}// 打印验证是否成功for (int i = 0; i < card.averageSize; ++i) {qDebug() << "Data[" << i << "] = " << card.Average[i];}
}
函数的调用:
CardParameters CardSet;
CardSet.averageSize = 10;collectData(CardSet); // 传入引用,函数内的修改会影响外部变量// 现在可以安全访问 CardSet.Average
if (CardSet.Average != nullptr) {for (int i = 0; i < CardSet.averageSize; ++i) {qDebug() << "外部访问: " << CardSet.Average[i];}
}
在函数调用结束后,外部访问结构体中的变量,其值和函数体中赋值的结果一致。
两种情形在使用完指针变量后都需要手动释放内存,否则会出现内存泄漏。
delete[] CardSet.Average;
CardSet.Average = nullptr;
CardSet.averageSize = 0;
总结:
1. 值传递
定义:将结构体对象复制一份传入函数,函数内部操作的是副本。
特点:
特性 | 描述 |
---|---|
操作对象 | 函数内部操作的是原始结构体的一个拷贝 |
修改影响 | 不会影响外部原始结构体 |
性能开销 | 如果结构体较大,会有性能损耗(需要复制) |
生命周期 | 副本生命周期仅限于函数体内,函数结束后自动销毁 |
省流:在函数体中修改结构体中的任何变量,在函数调用结束后自动失效
如果结构体中含有指针,并且没有深拷贝逻辑,容易造成浅拷贝问题(两个指针指向同一块内存)。
2. 引用传递
定义:将结构体变量的引用传入函数,函数内部操作的是原始对象本身。
特点:
特性 | 描述 |
---|---|
操作对象 | 函数内部直接操作原始结构体 |
修改影响 | 会直接影响外部原始结构体 |
性能开销 | 高效,不需要复制整个结构体 |
生命周期 | 不涉及副本,不影响原始对象生命周期 |
在函数体中修改结构体中的任何变量,在函数调用结束后值仍然保留,适合用于数据采集、配置设置等需要“输出”的场景