析构函数 undefined reference_C++ Lesson 3:函数与类

22fdfada56992d6e89532a60f1b03853.png

终于来到了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

输出结果:

419b351f254a184732a3b4184e33de7a.png

因此,这里void定义的函数就是打印了一行字。

  • 函数①不传参()函数括号依然要写、②有返回值
int 

这个函数①不传参,因此f()括号为空,括号依然要写,②但是有返回值——注意,返回值的类型(例如这里是int)要和定义函数的类型(int f())一样。

  • 函数①传参+②返回值
int 

这个函数①传参的同时定义好数据类型(两个整数x和y),②返回整数型。

2、主函数main

主函数为什么返回0?这里,0是status code,告诉系统我们有没有操作成功;那么0表示执行成功,非0表示没有执行成功。

3、函数的重复使用

#include

结果为

2e919e7911eb1ca6949a36f74c052044.png

这里例子中,我们重复使用了get_value_from_user这个函数。

  • factor函数
#include

运行结果:

0dbeb1f9643f6697f74c5bb54e78f9f2.png

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.

参考

  1. ^https://www.learncpp.com/cpp-tutorial/810-const-class-objects-and-member-functions/
  2. ^https://www.quantstart.com/articles/Passing-By-Reference-To-Const-in-C/
  3. ^https://zhuanlan.zhihu.com/p/141654835
  4. ^https://www.geeksforgeeks.org/passing-by-pointer-vs-passing-by-reference-in-c/
  5. ^https://zhuanlan.zhihu.com/p/141654835
  6. ^https://www.tutorialspoint.com/const-member-functions-in-cplusplus
  7. ^https://www.learncpp.com/cpp-tutorial/8-5a-constructor-member-initializer-lists/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值