终于来到了C++第一个重点内容:函数function和类class的搭建。当时学习的时候,也花了很多时间才搞定了这一块。。。其实反过来看,因为当时花了时间,打好了基础,现在复习速度应该很快。希望大家都能够搞懂这一块。
主要内容:
- 函数
- 传参、返回值
- 按值传递
- 按引用传递
- 按引用常量传递
- 按指针传递
- 类
- 声明和定义分开
- 构造器
- 成员初始化列表——member initializer lists
- 操作符重载
- 友元函数
- 对象析构
- enum within class
- const class objects and member functions[1]
- const classes
- const menber functions
- const references
- Overloading const and non-const function
- const reference to const member function
一、函数
- 函数的定义:能够重复使用的statements的序列,以用来做固定的工作。
- 定义函数的类型,和返回的数据类型一致。例如float f(),那么return的结果也要是float。例如,我们前面的int main(),最后返回的0也是int型。
- 不返回,用void定义函数。
- 注意:函数中不能够再嵌套定义其他函数。
类型
- void:如果函数中什么都不返回,那么在定义的时候,采用void。
void
这个函数①既不传参、②也不返回值,就是个单独操作。
例如,
#include
输出结果:
因此,这里void定义的函数就是打印了一行字。
- 函数①不传参()函数括号依然要写、②有返回值
int
这个函数①不传参,因此f()括号为空,括号依然要写,②但是有返回值——注意,返回值的类型(例如这里是int)要和定义函数的类型(int f())一样。
- 函数①传参+②返回值
int
这个函数①传参的同时定义好数据类型(两个整数x和y),②返回整数型。
2、主函数main
主函数为什么返回0?这里,0是status code,告诉系统我们有没有操作成功;那么0表示执行成功,非0表示没有执行成功。
3、函数的重复使用
#include
结果为
这里例子中,我们重复使用了get_value_from_user这个函数。
- factor函数
#include
运行结果:
4、各种传递以及const &——passde by reference to const in C++[2]
- 这个很重要,因为下面类的定义中,需要不停使用。
- 存在不同的方法将数据(data)/对象(object)传递给函数——
- 传递方法:传值(value),引用传递(reference),指针传递(pointer)。这三种方法各有自己的效率(efficiency)、储存(storage)以及行为(behavior)。
- 定义main函数中的变量称为本体(underlying),自己定义函数中的变量称为传递变量(passed),就这么先理解吧。。。
(1)按值传递(passed by value)
- 对本体a进行复制(copy)得到副本a1,然后对副本a1进行了操作,和本体无关。
- 例子,定义一个欧几里得距离的函数。
#include
运行结果
This is e-norm : 5
Let's see if vec is changed:
3 4
在这个函数中,euclid_norm(vector<double> vec)是直接将main函数中的向量vec111进行了复制、同时是对复制的值进行了操作,而不是对本体vec111本身进行了操作,因此euclid_norm函数中对副本的操作、不会影响本体。比如,我们在取模的同时,修改了副本的值,但是最后本体没有受到影响。
- 按值传递方式缺陷:expensive,not ideal, not imply correct usage。
(2)按引用传递(passed by reference)
- 函数是直接对本体进行操作,没有copy。
- 回忆按引用传递的定义:int &ref = a。ref是a的本体,&ref是a的地址=&a。
- 既节省内存,又节省了CPU的循环,因为不用给副本分配空间。效率相当高。
- 如果在函数中对变量进行了修改,那么最后本体也会发生同样的改变——因为这里传递变量就是本体。。
- 再以欧几里得距离为例子,只改一个地方,其他不变。
...
结果
This is e-norm : 5
Let's see if vec is changed:
1 1
最后向量变了。
- 缺点:有时候,我们并不想修改本体
- 按引用传递模板[3]:
auto
(3)按值传递到常量(passed by reference to const)
- 先来张总结表——为啥要用pass by reference to const
- 为了融合①按值传递的优点(不修改本体)②按引用传递的优点(效率高)的综合优点,避开①按值传递的缺点(copy导致效率低)②按引用传递的缺点(修改本体)的综合缺点,因此发明了pass by reference to const技术。
- pass by reference to const:和by reference,基本模板和reference一样,但是把本体标记为const,不能在自己定义的函数中修改。
- const作用:声明 按引用传递的变量绝对不能够改变。
- 依然是欧几里得的例子
- 如果我只修改一行
...
编译的时候会报错:提示我们修改向量是错误的~
c3.cpp: In function ‘double euclid_norm(const std::vector<double>&)’:
c3.cpp:12:12: error: assignment of read-only location ‘(& vec)->std::vector<double>::operator[](((std::vector<double>::size_type)i))’
12 | vec[i] = 1;
因为我①已经声明了vec是一个const就不能改变,②偏偏想要改——两者冲突而报错。
因此正确的情况是
#include
这样就对了。
(4)按指针传递[4](passed by pointer)
- 本质上和按引用传递一样,都是对本体直接操作。
- 回忆定义指针:int *p = &a。*p是a的本体,p是a的地址=&a。
- 例子
#include
结果为:
x = 1, y = 2.
swap1 : x = 1, y = 2.
swap2 : x = 2, y = 1.
swap3 : x = 2, y = 1.
其中swap1是按值传递,swap2是按指针传递,swap3是按引用传递。2和3均是直接对本体进行了操作。
- 按引用传递的格式[5]
auto
-_-||。。初学者应该反复搞清楚各种格式。。
二、类
1、一个对象(object)就是一个类的具体实例,一个类能够视为一个类型,一个对象能够看做变量。vector和string都是c++的类。
2、类分为数据成员和函数成员。这个其实分别对应着python中定义类的属性(attribute)和方法(method),Python中定义类模板
class
那么init对应的其实就是C++中的private的数据成员,定义的方法对应C++的public的函数成员。
3、模板
- 定义类的一般格式是
class
- class是保留关键字,用于类的开头。
- className是定义类的名称。
- member1、member2...是类的成员变量或者函数。
- access_specifier是访问限制说明,可以选择的访问权限有private、public、protected。
- 注意最后是以分号结束。
4、例子
#include
说明:
- 定义类:声明定义一个名字叫Rectangle的类(即为一个类型),类中包含4个成员:
- 具有private访问权限的两个int型数据成员(成员width和成员height);
- 具有public访问权限的两个成员函数:成员函数set_values和area。
- 定义对象:
- Rectangle rect;//生成一个对象,名叫rect。类似于int a,这里a就是整数型对象。
- 访问对象的public成员:rect.set_values(3,4) //使用 点 链接 对象 & 成员函数。就类似于vector<int> v1, v1.push_back(1),这里push_back就是成员函数。
- 不能够从外部访问rect的private成员,例如,我们不能够通过 ret.width=3赋值。
- 注意:在写类的时候,很多分号(;)不能够省略。
5、成员函数的申明和定义分离
- 使用类的时候,没有必要管理是怎么实现的,可以吧函数的事先放在类外面。
- 例子:
#include
说明:
- 在class内部,先写了void set_values(int, int),这里两个int定义不能够省略;
- 在class外部,使用了scope operator ::,用于定义函数。运算符 :: 用于指定函数是类Rectangle的成员函数。
6、构造器(constructor)
- 一个类包括了一个特殊的函数,叫做构造器。
- 作用:当创建该类新的对象的时候,该函数就能够被自动调用,从而初始化成员变量或者分配储存空间。
- 使用:构造器名称和类一样,同名函数;
- 没有任何返回类型(void,因此构造器之前的void省略)。
- 例子1
#include
这样,就省略了void set_values(int, int )的操作,就能够在生成对象的时候,顺便把参数一起传进去-——构造器完成初始化。
- 例子2——默认构造器
#include
结果为:
This is default r1 circum : 6.28319 and area : 3.14159
This is default r2 circum : 12.5664 and area : 12.5664
这里出现了默认构造器:默认构造器不用传参。
- 例子3:默认构造器&set_values同时出现
#include
结果为:
This is c1 circum : 6.28319 and area : 3.14159
This is c2 circum : 12.5664 and area : 12.5664
This is c3 circum : 18.8496 and area : 28.2743
c1使用的是默认构造器,c2使用的是set_values传值,c3使用的是构造器传值。
7、复数类
- 完成复数的定义、复数加法和乘法。
-
- 代码
#include
结果为
z1 :1+1i
z2 :1-1i
z3 :1+2i
z4 :1
z2+z3 :2+1i
z2*z3 :3+1i
注意点:①在类的函数定义中,因为函数计算的结果返回的还是类,因此使用complex定义函数,有点递归的意思在里面;②这里使用了“按引用传递常量”(const complex &);如果我改成“按引用传递”(complex &),也是得到一样的结果,因为我没有在自己定义的函数中对本体进行修改。
8、const menber functions[6]-type func_name const {}
成员函数声明为常数型const,从而这些成员函数不能够被修改。
class
这里,getValue成员函数是返回值,没有修改,因此通过const写在函数名后面。
9、成员初始化列表[7]——member initializer lists
- 在后面的继承中的编写中,这个是基础。
(1)变量赋值以及初始化——三种方式①copy,②直接赋值,③通过uniform initialization。
int
(2)使用初始化列表(initialization list)= ①直接初始化 or ② uniforn初始化。
- 模板:
//默认构造器
- 例子
#include
something1通过assignments进行赋值,something2通过initialization list赋值。结果一样。
This is sth1 : 1, 2.2, c
This is sth2 : 1, 2.2, c
This is sth2 : 10, 10.9, t
三、操作符重载(operator overload)
- 引言:当我们自己定义类的时候,需要为类定义自己的操作符。
- 上面我们在类中定义复数的加法和乘法的时候,并没有使用+ * 作为运算符,而是使用的.sum和.multiply进行运算。事实上,我们能够重新定义+ * ,使得复数也能够进行相关运算。例如,我希望笛卡尔向量加和(3, 1) + (1, 2) = (4, 3)
- 基本类型的变量应用于加法、赋值、输出运算。操作符重载为“类”实现此类运算提供了可能。
-
1、例子:笛卡尔向量求和运算(3, 1) + (1, 2) = (4, 3)
- 代码
#include
结果:
This is random1 : (5.8725e-37, 4.57188e-41)
This is (3,1) + (1,2) : (4, 3)
Try to change random1 : (10, 10)
①默认构造器,没有设定初始值,因此随机生成的random1是随机数;②重载了加法运算,适合笛卡尔向量相加;③当我们把“类”的x和y放在public中定义的时候,x和y能够直接访问且修改,random.x和random.y。
如果将x和y设定为private,那么就没法直接从外部修改x和y的值。
#include
哪怕不修改random.x=10,只要还想在main函数中直接访问random1.x,都会报错(但是自己定义的加法运算中,result.x = x+param.x,这个是可以的)。
2、友元函数Friend:能够访问private
- 如果我们把x和y私有了,那么能通过友元函数访问私有成员。
- 上例的新代码
#include
注意:友元函数定义的加法运算中,和上面的略有区别;同时,可以看到我们能够通过.x和.y直接访问私有变量。
- 例子2:复数的运算重载
#include
结果:
z1 : 1+1i
z2 : 1-1i
z3 : 1+2i
z4 : 1
z2+z3 : 2+1i
z2*z3 : 3+1i
四、对象析构(destructor)
1、作用:释放对象;当一个对象结束了生存期(例如自己定义的函数中变量在调用之后就结束),系统自动调用析构函数,释放该对象,从而释放内存空间。
2、特点:
- 在类中,是成员函数的一种。
- 和类同名,但是函数前加上~,表示功能和构造器相反。
- no 参数,不能重载,no 返回值。
- 在对象生命期即将结束时,被系统自动调用。
- 可以是虚函数(啥事虚函数。。。)
- 变量消除顺序是:后进先出,由最后的往最开始的消除。
3、例子
#include
结果
This is z1 : 1+1i
This is z2 + z3 : 2+1i
This is z2 * z3 : 3+1i
Destructor happens : 3 + (1)i is destroyed.
Destructor happens : 2 + (1)i is destroyed.
Destructor happens : 1 + (2)i is destroyed.
Destructor happens : 1 + (-1)i is destroyed.
Destructor happens : 1 + (1)i is destroyed.
分析:
①destructor和constructor构造类似,只是多了个“~”; ②消除顺序是 最后生成的热阿result2→倒数第二个生成的result1→z3→z2→z1,挺有趣的,有点后进先出(LIFO)的意思。
五、eunm within classes
- 例子
#include
- 注意点
- ①在Car的类中,enum又定义一个“小类”-Color,值为red blue white;
- ②涉及到Color的实例化,都要写上 Car::Color;
- ②定义顺序:先定义的是public——enum Color——构造器Car,然后定义的是private——Car::Color;这是因为先得让Car大部分构造完毕之后,再能够使用Car::Color。
六、const class objects and member functions
1、const classes
- main函数中,将类的对象进行const化——从而成员变量不可修改(即便是public也不可以修改),不是在类的定义中使用const。
class
在这个例子中,main函数中使用了默认的构造器,sth=0;同时前面加上了const,表示sth=0将无法改为其他值,即便它是publice也无法修改,sth.setValue(1)也不行,sth.m_value=1也不行。
2、const member functions
- 定义:这个成员函数,保证①不会修改对象的值,②也不会调用其他non-const成员函数。
- 上面例子中,不但不能够修改,也不能够通过sth.getValue()访问它的值——即便没有修改。
- const class的对象,只能够调用const member functions。
#include
对比,sth1是普通类型,两种getValue都能够运行;const class型的sth2,只能够运行const member function。
3、const references
- 传值将会导致进程缓慢,但是如果引用原参数将会避免copy。
- 将reference设为const,保证函数不会修改参数。
- 例子
#include
4、Overloading const and non-const function
例子
#include
最后,sth1变成“Hi”,sth2变成空格。sth1能够自动对应第二个getValue,sth2能够自动对应第一个getValue。
5、Const reference to const member function
- 在上面的例子中,const string& getValue() const {return m_value;},这句话中存在好几个值得注意的点;
- ①第一个const,是函数的返回类型;因为在定义类的构造器Something::Something(const string& value =" ")传参是const string &,那么函数的返回类型,即为const reference。
- ②&,表示返回的是原始值(reference),而不是copy。
- ③第二个const,表示成员函数不会改变对象的状态,用于const对象。
参考书籍:
C++程序设计/郑莉,李超编著.——北京:机械工业出版社,2012.1.
参考
- ^https://www.learncpp.com/cpp-tutorial/810-const-class-objects-and-member-functions/
- ^https://www.quantstart.com/articles/Passing-By-Reference-To-Const-in-C/
- ^https://zhuanlan.zhihu.com/p/141654835
- ^https://www.geeksforgeeks.org/passing-by-pointer-vs-passing-by-reference-in-c/
- ^https://zhuanlan.zhihu.com/p/141654835
- ^https://www.tutorialspoint.com/const-member-functions-in-cplusplus
- ^https://www.learncpp.com/cpp-tutorial/8-5a-constructor-member-initializer-lists/