目录
第一节:引用
1-1.引用的定义
引用的作用是给变量取别名,新的名字和旧的名字指向同一块空间,需要利用 & 符号,以int类型为例:
int a = 0;
int& b = a; // 给a取别名
因为a、b都指向同一块空间,所以修改任何一方,另一方也会修改:
#include <iostream>
int main()
{
int a = 0;
int& b = a; // 给a取别名
a = 1; // 修改a为1
std::cout << "b=" << b << std::endl;
b = 2; // 修改b为2
std::cout << "a=" << a << std::endl;
return 0;
}
1-2.引用的注意事项
(1)引用必须初始化:
int& b; // 这样的写法是错误的
(2)引用不能改变指向:
例如上面的b是a的引用,那么b永远都只能指向a的空间,不能像指针一样改变指向。
(3)一个变量可以有多个引用,且引用的引用还是我的引用:
int a = 0;
int& b = a;
int& c = a;
int& d = b;
上述代码中的a三个引用b、c、d,虽然d引用的是b,但是b是a的引用,它们就指向同一块空间,所以d也是a的引用。
1-3.引用的使用场景
(1)作为函数参数
作为函数参数时,引用和指针的作用类似,都是减少拷贝:
int Add(int& x, int& y)
{
return x + y;
}
int main()
{
int a = 1;
int b = 2;
Add(a,b);
return 0;
}
上述代码中,x就是a的引用,y就是b的引用。
(2)代替指针使用复杂的场景
因访问、修改指针指向空间的值都需要解引用,如果有多级指针的情况,就可能会让情况很复杂,使用引用可在一定程度上增强代码可读性。
(3)作返回值
作返回值时可以修改返回值,所以这个返回值需要在出函数时不销毁(全局变量,静态变量,堆上的变量等)
int a = 0;
int& func()
{
return a; // 返回a的引用
}
int main()
{
func() = 1; // 修改a的引用
std::cout << "a=" << a << std::endl;
return 0;
}
1-4.引用和指针的联系
引用是别名,语法上不开辟空间,指针是变量类型,要额外开空间,但是引用的底层是指针,所以实际上要开空间;
引用必须初始化,指针可以初始化,也可以不初始化;
引用不可以改变指向的对象,指针可以改变指向的对象;
引用无空引用,但是有野引用;指针有空指针,有野指针;
指针有多级指针,但是引用没有多级引用,因为一个变量的引用的引用还是它本身的引用。
第二节:成员函数
在C语言的结构体中,只能够存放各种变量,但是在C++中,结构体还能存放函数:
struct calculator
{
int a = 0;
int Add(int x,int y)
{
return x + y;
}
};
上述代码就定义了一个结构体类型 calculator,它有一个变量a和一个函数Add,这两个都属于类 calculator的成员,如果是函数就叫成员函数,如果是变量就叫成员变量。
那么这个成员函数如何调用呢?
(1)我们可以定义一个结构体的实例来调用:
#include <iostream>
struct calculator
{
int a = 0;
int Add(int x,int y)
{
return x + y;
}
};
int main()
{
calculator cal; // 定义一个实例
std::cout << cal.Add(1, 2) << std::endl;
return 0;
}
(2)使用一个匿名实例调用函数:
int main()
{
std::cout << calculator().Add(1,2) << std::endl;
return 0;
}
上述代码的 calculator() 就是创建了一个没有名字的实例,然后调用了它的 Add 函数,这种实例的生命周期只在当前语句,用完就可以自动销毁。
那么为什么 类() 能够创建一个类呢,我们以后会讲。
(3)使用类域访问静态函数
一个结构体的类型被创建好后,可以用它定义许多实例,这些实例拥有的函数是相同的,所以为了节约空间,这些实例的函数都被存放在一块公共区域中。这些实例调用函数时实际上是去公共的空间中调用同一个函数。
公共区域可以认为是一块命名空间,访问函数的方式和命名空间相同,但是函数需要用 static 才能够访问到:
#include <iostream>
class calculator
{
public:
int a = 1;
static int Add(int x,int y) // static 修饰
{
return x + y;
}
};
int main()
{
std::cout << calculator::Add(1,2) << std::endl;
return 0;
}
所以实际上结构体在计算大小时,不能把函数计算在内,且结构体最小为1字节:
#include <iostream>
struct calculator
{
int a;
int b;
};
struct student
{
};
int main()
{
std::cout << sizeof calculator << std::endl;
std::cout << sizeof student << std::endl;
return 0;
}
C++中还有一个关键字 class ,它可以定义一些类类型,简称类。
类的使用方法和结构体一模一样,类与结构体只有细微的差别,而且C++主要使用类。
class calculator // 把struct换成class
{
public: // 加上这条语句
int a = 1;
int Add(int x,int y)
{
return x + y;
}
};
上述代码中的public的作用以后会讲,以后我们大多数情况只使用类了。
那么在类/结构体中添加函数有什么用呢,这就不得不讲到面向过程和面向对象编程了。
第三节:面向过程与面向对象
面向过程:编程的主体是一个一个的步骤,每一个步骤用一个或者多个函数实现,需要的时候一个一个的调用相应的函数。C语言就是经典的面向过程的语言。
面向对象:编程的主体是一个对象,这个对象可以完成一个或者一部分工作,这部分工作所需的函数在这个对象中,这个对象就是一个类的实例。C++就是一个面向对象的语言。
例如现在的任务是完成多个式子的计算:1+2、2*3、8/4、3-2。我们需要用到+-*/的运算,
如果用C语言就需要写4个函数:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int Mul(int x, int y)
{
return x * y;
}
如果使用C++,就可以把这4个函数全部变成成员函数:
class calculator // 把struct换成class即可
{
public:
int Add(int x, int y)
{
return x + y;
}
int Sub(int x, int y)
{
return x - y;
}
int Div(int x, int y)
{
return x / y;
}
int Mul(int x, int y)
{
return x * y;
}
};
面向过程编程适合简单、短小的程序,而面向对象编程更适合大型、复杂的系统开发。
当然,并不是说C++不能面向过程编程,实际上C++兼容C语言99%的写法,只是有了更多的选择。
第四节:inline函数
inline函数又叫内联函数,它的特征是调用它时不建立栈帧,而是在原地展开,即让代码量更多,但是减轻栈帧的压力。
inline函数需要用到 inline 关键字:
inline int Add(int x,int y)
{
return x + y;
}
因为inline函数原地展开的特性,决定它不合适递归函数,因为展开太多会导致可执行文件很大。
当函数内容过多时某些编译器也不会展开它,而是像普通函数一样建立栈帧,这是某些编译器会进行的优化。
inline函数也支持声明与定义分离:
inline int Add(int x, int y); // 函数声明
inline int Add(int x,int y) // 函数定义
{
return x + y;
}
第五节:auto关键字
5-1.基本用法
在python语言中,定义变量时就不需要指明变量的类型,这是因为它会自动推导变量的类型,C++中引入了auto关键字,它就可以自动推导类型:
#include <iostream>
int main()
{
int a = 0;
auto b = a;
std::cout << typeid(b).name() << std::endl; // typeid().name()可得到变量的类型,以字符串的形式返回。
return 0;
}
在上述代码中,因为a的类型是int,用它初始化b时,b的类型自动被推导成int。
其次,还可以自动推导引用类型:
#include <iostream>
int main()
{
int a = 0;
auto& b = a; // 引用
std::cout << typeid(b).name() << std::endl; // typeid().name()可得到变量的类型
return 0;
}
此时b是a的引用,它们指向同一块空间,类型都是int。
最后,auto可以加*指定为指针类型,只能传指针:
#include <iostream>
int main()
{
int a = 0;
auto* b = &a; // 指针
std::cout << typeid(b).name() << std::endl; // typeid().name()可得到变量的类型
return 0;
}
5-2.使用场景
5-2-1.复杂类型赋值
在变量类型写起来很复杂时,auto就可以发挥功效了,例如给函数指针赋值时,本来需要写成:
void(*pfunc)(int,int) = func;
使用auto就可以写成:
auto pfunc = func;
5-2-2.范围for
其次auto还可以与C++的容器实现范围for:
int main()
{
int arr[] = {1,2,3,4,5,6,7,8,9};
for (auto num : arr) // 范围for
{
std::cout << num << " ";
}
return 0;
}
上述代码中的数组arr就是一种容器,实际上C++还引入了许多其他的容器,都可以使用范围for,这个以后会讲到;
auto 自动推导变量num的类型(上述代码是int,如果知道元素类型用具体类型不用auto也可以),num是arr从头到尾元素的拷贝,如果用auto&就是元素别名,就可以修改元素;
综上所述,范围for就是一种C++引入的遍历容器的手段。
范围for的范围必须是确定的,例如将数组传参时它会变成指针,那么它的范围就不确定了,就会报错:
5-2-3.函数
在函数中,auto不能作为形参的类型:
auto可以作为返回类型,它会自动推导返回值的类型:
#include <iostream>
auto Add(int x,int y) // auto作返回类型
{
return x + y;
}
int main()
{
std::cout << typeid(Add(1,2)).name() << std::endl; // typeid().name()可得到变量的类型
return 0;
}
但是这种写法并不好,因为如果嵌套多个函数时,要得知最外层函数的返回值到底是什么类型就很麻烦,所以作返回值的写法不建议。
下期预告:
下一次我们将具体讲讲类calss和对象,包括以下内容:
类和对象的访问权限;
成员函数的声明与定义分离;
面向对象的三大特性之一:封装;
隐含的this指针;
类的6个默认函数;