1 引用的应用
1.1 数据传递--值传递
C语言只要涉及数据传递(例如:初始化、赋值、传参、返回值),
都为值传递(将数据复制一份给到目标内存)。
// value.cpp 值传递:将数据复制一份给别人
#include <iostream>
using namespace std;
void foo( int x ) { }
int bar( ) {
int m = 8888;
return m;
}
int main( void ) {
int a = 10; // 初始化
a = 20; // 赋值
foo( a ); // 传参
/*|8888|*/bar( ); // 返回值
return 0;
}
C++语言因为有引用,可以不做值传递。
1)引用型形参:函数的形参是实参的别名,避免对象复制的开销。
非常引用型参数,可在函数中修改实参值;
常引用型参数,防止了对实参的意外修改, 也接受常量型实参(右值)。
// vparam.cpp 引用 作为函数的 形参
// 当我们在设计一个函数时,只要可以确定在函数内部绝对不修改实参,那么大胆的加上const
#include <iostream>
using namespace std;
void Swap( int* x, int* y ) { //
int z = *x;
*x = *y;
*y = z;
}
void Swap( int& x, int& y ) { // 非常引用型 形参:函数内部可以直接修改实参
int z = x;
x = y;
y = z;
}
void Printer( const int& x, const int& y ) { // 常引用型 形参:能够防止意外修改实参
// x = 888; // 编译器会提醒我们read-only (打印机就应该原样打印哦)
cout << x << ' ' << y << endl;
}
int main( void ) {
int a=10, b=20;
Swap( a, b );
// Swap( &a, &b ); // 这种写法并不直观
cout << "a=" << a << ", b=" << b << endl;
Print( a, b );
Print( 888, 999 ); //接受常亮型实参(右值)
return 0;
}
2)引用型的返回值:从函数中返回引用,一定要保证函数返回后,该引用的目标依然有效
-可以返回全局、静态变量的引用
-可以返回在堆中动态创建(new)的对象的引用
-可以返回引用型参数本身
-可以返回成员变量的引用
-可以返回调用对象自身的引用
-不能返回局部变量的引用
非常引用型返回值:通过引用可以修改目标
常引用型返回值:通过引用不能修改目标
// vreturn.cpp 引用 作为函数的 返回值
#include <iostream>
using namespace std;
int g_value = 0;
int& foo() { // 非常引用型 返回值:可以通过引用修改目标
return g_value;
}
const int& fooo() { // 常引用型 返回值:不可以通过引用修改目标
return g_value;
}
int& bar() {
static int s_value=0;//这行代码是在程序启动时就执行,而且只执行1次,不是每次调bar都执行
cout << "s_value=" << s_value << endl;
return s_value;
}
int& hum() {
int* p = new int;
return *p;
}
int& fun( int& x ) {
// ...
return x; // 返回 引用型 参数本身
}
/*
int& boo() {
int m = 666;
return m; // 不要返回局部变量的引用
}
*/
int main( void ) {
foo() = 100;
cout << "g_value=" << g_value << endl;
// fooo() = 888; // readonly - error
bar() = 200;
bar();
hum() = 300;
int a_value = 0;
fun(a_value) = 400;
cout << "a_value=" << a_value << endl;
// boo();
return 0;
}
在实现(汇编)层面,引用就是指针,但在C++语言层面,引用不是实体类型,由此推导出,C++语言层面,引用与指针存在明显的差别:
1)指针可以不初始化,其目标可在初始化后随意变更(指针常量除外),
引用必须初始化,且一旦初始化就无法变更其目标。
2)存在空指针,不存在空引用。
3)存在指向指针的指针,不存在引用的引用。
4)存在指针的引用,不存在引用的指针。
5)存在指针数组,不存在引用数组,但存在数组引用。
// poi_ref 指针 和 引用 的差别
#include <iostream>
using namespace std;
int main( void ) {
int a=10, b=20;
int* pa = &a; // 指针可以不做初始化,也可以做初始化
pa = &b; // 指针可以随意变更它所指向的目标内存
int& ra = a; // 引用必须初始化
ra = b; // 引用不可以随意变更它所代表的目标内存( ra仍然是a的别名 )
pa = NULL; // 存在空指针
// ra = NULL; // 不存在空引用
int** ppa = &pa; // 存在二级指针
// int&& rra = ra; // 不存在二级引用
int* &rpa = pa; // 存在指针的引用 !!
// int& *pra = &ra; // 不存在引用的指针
int* p = &ra; // 这里取的并不是引用本身的地址,而是引用的目标内存a的地址
int x, y, z;
int*parr[3] = {&x, &y, &z}; // 存在指针的数组
// int&rarr[3] = {x,y,z}; // 不存在引用的数组
int arr[3];
int(&rr)[3] = arr; // 存在数组的引用
return 0;
}
2 显示类型转换(强转)
C风格的显示类型转换: (int)a
C++风格的显示类型转换: int(a)
// convert_pre.cpp 显式(强制)类型转换
#include <iostream>
using namespace std;
int main( void ) {
int a; double b; float c; short d; char e;
// 任何基本类型变量之间都可以 隐式转换
a = b = c = d = e;
e = d = c = b = a;
// 任何其他类型的指针 到 void* 都可以隐式转换
void* pv = &a;
pv = &b;
pv = &c;
pv = &d;
pv = &e;
// void* 到 任何其他类型的指针 都必须强转(显式转换)
int* pa = (int*)(pv);
double* pb = (double*)(pv);
float* pc = (float*)(pv);
short* pd = (short*)(pv);
char* pe = (char*)(pv);
// 任何类型的 非常指针 到 同类型的常指针 都可以隐式转换
const int* cpa = pa;
const double* cpb = pb;
const float* cpc = pc;
const short* cpd = pd;
const char* cpe = pe;
// 任何类型的 常指针 到 同类型非常指针 都必须强转
pa = (int*)(cpa);
pb = (double*)(cpb);
pc = (float*)(cpc);
pd = (short*)(cpd);
pe = (char*)(cpe);
return 0;
}
3 温和强转
3.1 静态类型转换
static_cast<目标类型>(源类型变量)
隐式类型转换的逆转换(去常除外)
自定义类型转换
3.2 动态类型转换
dynamic_cast<目标类型>(源类型变量)
多态父子类指针或引用之间的转换
3.3 常类型转换
const_cast<目标类型>(源类型变量)
去除指针或引用上的const属性
3.4 重解释类型转换
reinterpret_cast<目标类型>(源类型变量)
任意类型的指针之间的转换
任意类型的引用之间的转换
任意类型的指针和整型之间的转换
// convert.cpp 显式(强制)类型转换
#include <iostream>
using namespace std;
int main( void ) {
int a; double b; float c; short d; char e;
// 任何基本类型变量之间都可以 隐式转换
a = b = c = d = e;
e = d = c = b = a;
// 任何其他类型的指针 到 void* 都可以隐式转换
void* pv = &a; // int*->void*
pv = &b;
pv = &c;
pv = &d;
pv = &e;
// void* 到 任何其他类型的指针 都必须强转(显式转换)*************
int* pa = static_cast<int*>(pv); // void*->int*的反向int*->void*可以隐式
double* pb = static_cast<double*>(pv);
float* pc = static_cast<float*>(pv);
short* pd = static_cast<short*>(pv);
char* pe = static_cast<char*>(pv);
// 任何类型的 非常指针 到 同类型的常指针 都可以隐式转换
const int* cpa = pa; // int*-->const int*
const double* cpb = pb;
const float* cpc = pc;
const short* cpd = pd;
const char* cpe = pe;
// 任何类型的 常指针 到 同类型非常指针 都必须强转***************
pa = const_cast<int*>(cpa); // 去常属性
pb = const_cast<double*>(cpb);
pc = const_cast<float*>(cpc);
pd = const_cast<short*>(cpd);
pe = const_cast<char*>(cpe);
// 除了void*外,其他任何类型的指针之间都必须强转*************
pa = reinterpret_cast<int*>(pb); // double*-->int*
pb = reinterpret_cast<double*>(1000); // int-->double*
return 0;
}
4 面向对象
4.1 what
一切皆对象。
把大型软件看成是由多个对象组成的团推。
对象拥有足够的智能,能够理解来自其他对象的信息,并以适当的行为作出反应。
面向对象的三大特征:封装、继承、多态。
4.2 why
相比分而治之的结构化程序设计,强调大处着眼的面向对象程序设计思想,更适合开发大型软件。
得益于代码复用等面向对象的固有特征,软件开发的效率获得极大提升,成本却大幅降低。
4.3 how
至少掌握一种面向对象的程序设计语言,如C++
深入理解封装、继承、多态等面向对象的重要概念
学习设计模式,源自多年成功经验的积累总结。
第三个错误:等号右边是右值,故等号左边应为万能引用int* const &r :
//test.cpp
#include <iostream>
using namespace std;
int main( void ) {
int arr[9] = {9,3,1,4,2,7,5,8,6};
int* p = arr; // 正确 &arr[0]
int(*pp)[9] = &arr; // 正确
// int* &r = arr; // 错误 &arr[0]
int* const& r = arr;// 正确
int(&rr)[9] = arr; // 正确
}