一、链表
1. 链表的概述
数组和链表的优缺点
静态数组:int arr [5]; 必须事先确定数组元素的个数,过多浪费,过小容易溢出,删除插入数据效率低(需要移动大量的数据)
动态数组:不需要事先知道元素的个数,在使用中动态申请,删除插入效率低(需要移动大量的数据)
(数组优点:遍历元素效率高)
链表:不需要事先知道数据的个数,在使用中动态申请,插入删除不需要移动数据
(链表缺点:遍历元素效率低)
概述
链表是由一个个节点组成,节点没有名字,每个节点从堆区动态申请,节点间物理上是非连续的,但是每个节点通过指针域保存下一个节点的位置达到逻辑上的连续
2. 静态链表
#include<iostream>
using namespace std;
struct Stu
{
// 数据域
int num;
char name[32];
// 指针域
struct Stu *next;
};
int main(int argc, char *argv[])
{
struct Stu node1 = {101,"lucy",NULL};
struct Stu node2 = {102,"bob",NULL};
struct Stu node3 = {103,"jenny",NULL};
struct Stu node4 = {104,"danny",NULL};
struct Stu node5 = {105,"tom",NULL};
// 定义链表头
struct Stu *head = &node1;
node1.next = &node2;
node2.next = &node3;
node3.next = &node4;
node4.next = &node5;
node5.next = NULL;
// 遍历
struct Stu *pb = head;
while(pb!=NULL)
{
// 访问数据
cout << pb->num << " " << pb->name << endl;
// pb移动到下一个节点位置
pb = pb->next;
}
return 0;
}
3. 动态链表
以学生管理系统案例,完成动态链表的学习
新建link.cpp,link.h,link.cpp编写功能函数
①. main函数设计
main.cpp
#include <iostream>
#include "link.h"
#include <string.h>
#include <windows.h>
using namespace std;
Stu *head = NULL;
int main(int argc, char *argv[])
{
help();
while(1)
{
char cmd[64] = "";
cout << "请输入指令:";
cin >> cmd;
if(strcmp(cmd,"help")==0)
{
help();
}
else if(strcmp(cmd,"insert")==0)
{
cout << "请输入要插入学生的学号和姓名:";
Stu temp;
cin >> temp.num >> temp.name;
head = insertLink(head,temp);
}
else if(strcmp(cmd,"print")==0)
{
printLink(head);
}
else if(strcmp(cmd,"search")==0)
{
char stuname[32] = "";
cout << "请输入要查询的学生姓名:";
cin >> stuname;
Stu *ret = NULL;
ret = searchLink(head,stuname);
if(ret != NULL)
{
cout << "查询结果如下:" << endl;
cout << "学号:" << ret->num << " " << "姓名:" << ret->name << endl;
}
else
{
cout << "未查询到相关学生信息 ... ..." << endl;
}
}
else if(strcmp(cmd,"delete")==0)
{
int stunum = 0;
cout << "请输入要删除的学生学号:";
cin >> stunum;
head = deleteLink(head, stunum);
}
else if(strcmp(cmd,"free")==0)
{
head = freeLink(head);
}
else if(strcmp(cmd,"cls")==0)
{
system("cls");
}
else if(strcmp(cmd,"quit")==0)
{
cout << "正在退出 ... ..." << endl;
Sleep(2000);
cout << "退出成功 ... ..." << endl;
return 0;
}
else
{
cout << "请输入正确的指令 ... ..." << endl;
help();
}
}
}
②. link函数设计
link.cpp
#include "link.h"
void help(void)
{
cout << "-----------------------------" << endl;
cout << "| help:帮助信息 |" << endl;
cout << "| insert:插入链表节点 |" << endl;
cout << "| print:遍历链表 |" << endl;
cout << "| search:查询链表某个节点 |" << endl;
cout << "| delete:删除链表某个节点 |" << endl;
cout << "| free:释放整个链表 |" << endl;
cout << "| cls:清空屏幕 |" << endl;
cout << "| quit:退出程序 |" << endl;
cout << "-----------------------------" << endl;
}
// 插入:头部插入
#if 0
Stu *insertLink(Stu *head, Stu temp)
{
// 从堆区申请待插入的节点空间
Stu *pi = new Stu;
// 给空间赋值
*pi = temp;
pi->next = NULL;
// 判断链表是否存在
if(head == NULL)//不存在
{
head = pi;
}
else//链表存在
{
pi->next = head;
head = pi;
}
return head;
}
#endif
// 插入:尾部插入
#if 0
Stu *insertLink(Stu *head, Stu temp)
{
// 从堆区申请待插入的节点空间
Stu *pi = new Stu;
// 给空间赋值
*pi = temp;
pi->next = NULL;
// 判断链表是否存在
if(head == NULL)//不存在
{
head = pi;
}
else//链表存在
{
// 寻找尾节点
Stu *pb = head;
while(pb->next!=NULL)
{
pb = pb->next;
}
// 在尾节点插入pi
pb->next = pi;
}
return head;
}
#endif
// 插入:有序插入
#if 1
Stu *insertLink(Stu *head, Stu temp)
{
// 从堆区申请待插入的节点空间
Stu *pi = new Stu;
// 给空间赋值
*pi = temp;
pi->next = NULL;
// 判断链表是否存在
if(head == NULL)//不存在
{
head = pi;
}
else//链表存在
{
// 寻找插入点
Stu *pb=head, *pf=head;
while((pb->num < pi->num) && (pb->next != NULL))
{
// pf保存pb的位置
pf = pb;
// pb移动到下一个节点
pb = pb->next;
}
//判断插入点位置
if(pb->num >= pi->num)// 头部、中部插入
{
if(pb == head)// 头部插入
{
pi->next = head;
head = pi;
}
else// 中部插入
{
pf->next = pi;
pi->next = pb;
}
}
else// 尾部插入
{
pb->next = pi;
}
}
return head;
}
#endif
// 遍历链表
void printLink(Stu *head)
{
// 判断链表是否为空
if(head == NULL)
{
cout << "link not exist ... ..." << endl;
}
Stu *pb = head;
while(pb != NULL)
{
cout << "学号:" << pb->num << " " << "姓名:" << pb->name << endl;
pb = pb -> next;
}
}
// 查询链表某个节点
Stu *searchLink(Stu *head, char *stuname)
{
// 判断链表是否存在
if(head == NULL)
{
cout << "link not exist ... ..." << endl;
}
// 逐个节点查询
Stu *pb = head;
while((strcmp(pb->name,stuname) != 0) && (pb->next != NULL))
{
pb = pb->next;
}
if(strcmp(pb->name,stuname) == 0)
{
return pb;
}
return NULL;
}
// 删除链表某个节点
Stu *deleteLink(Stu *head, int stunum)
{
// 判断链表是否存在
if(head == NULL)
{
cout << "link not exist ... ..." << endl;
}
// 逐个节点比较,寻找删除点
Stu *pb = head, *pf = head;
while((pb->num != stunum) && (pb->next != NULL))
{
pf = pb;
pb = pb->next;
}
if(pb->num == stunum)
{
if(pb == head)// 头节点删除
{
head = head->next;
}
else// 中尾部删除节点
{
pf->next = pb->next;
}
delete pb;
cout << "相关学生信息删除成功 .. ..." << endl;
}
else
{
cout << "未找到到要删除的学生信息 ... ..." << endl;
}
return head;
}
// 释放整个链表
Stu *freeLink(Stu *head)
{
// 判断链表是否存在
if(head == NULL)
{
cout << "link not exist ... ..." << endl;
return head;
}
else
{
cout << "正在释放链表 ... ..." << endl;
}
// 逐个节点释放
Stu *pb = head;
while(pb != NULL)
{
head = head->next;
delete pb;
pb = head;
}
cout << "链表释放成功 ... ..." << endl;
return head;
}
③. link.h
#ifndef LINK_H
#define LINK_H
#include <iostream>
#include <string.h>
using namespace std;
// 定义链表节点
struct Stu
{
// 数据域
int num;
char name[32];
// 指针域
Stu *next;
};
extern void help(void);
extern Stu *insertLink(Stu *head, Stu temp);
extern void printLink(Stu *head);
extern Stu *searchLink(Stu *head, char *stuname);
extern Stu *deleteLink(Stu *head, int stunum);
extern Stu *freeLink(Stu *head);
#endif // LINK_H
二、C++对C的扩展
1. 面向对象编程概述
①. 面向过程
面向过程是一种以过程为中心的编程思想。通过分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以了。面向过程编程思想的核心:功能分解,自顶向下,逐层细化(程序=数据结构+算法)。面向过程编程语言存在的主要缺点是不符合人的思维习惯,而是要用计算机的思维方式去处理问题,而且面向过程编程语言重用性低,维护困难。
②. 面向对象
面向对象编程(Object—Oriented Programming)简称OOP技术,是计算机应用程序的一种新方法、新思想。过去的面向过程编程常常会导致所有的代码都包含在几个模块中,使程序难以阅读和维护。在做一些修改时常常牵一动百,使以后的开发和维护难以为继。而使用OOP技术,常常要使用许多代码模块,每个模块都只提供特定的功能,他们是彼此独立的,这样就增大了代码重用的几率,更加有利于软件的开发、维护和升级。
在面向对象中,算法与数据结构被看作是一个整体,称作对象,现实世界中任何类的对象都具有一定的属性和操作,也总能用数据结构与算法两者合一地来描述,所以可以用下面的等式来定义对象和程序:对象 = 算法 + 数据结构,程序 = 对象 + 对象 + 对象 + ......
从上面的等式可以看出,程序就是许多对象在计算机中相继表现自己,而对象则是一个个程序实体。面向对象编程思想的核心:应对变化,提高复用。
③. 面向对象的三大特点
封装:把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。类将成员变量和成员函数封装在类的内部,根据需要设置访问权限,通过成员函数管理内部状态。
继承:继承所表达的是类之间相关的关系,这种关系使得对象可以继承另外一类对象的特征和能力。继承的作用:避免公用代码的重复开发,减少代码和数据冗余
多态:多态性可以简单地概括为 “一个接口,多种方法”,字面意思为多种形态。程序在运行时才决定调用地函数,它是面向对象编程领域的核心概念。
2. 作用域运算符 ::
:: 解决归属问题(谁是谁的谁)
#include <iostream>
using namespace std;
// 定义全局变量a
int a = 10;
int main(int argc, char *argv[])
{
// 定义局部变量a
int a = 20;
cout << "全局变量a = " << ::a << endl; // 10
cout << "局部变量a = " << a << endl; // 20
return 0;
}
3. 命名空间 namespace
创建名字是程序设计过程中一项最基本的活动,当一个项目很大时,它会不可避免地包含大量名字。C++允许我们对名字的产生和名字地可见性进行控制。C语言可以通过static关键字来使得名字只在本编译单元内可见,在C++中通过一种命名空间来控制对名字的访问。
①. C++命名空间(namespace)
在C++中,名称(name)可以是符号常量、变量、函数、结构、枚举、类和对象等等。工程越大,名称互相冲突的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免在大规模程序设计中,以及程序员使用各种各样的C++库时,这些标识符的名称发生冲突,标准C++引入关键字namespace(命名空间/名字空间/名称空间),可以更好地控制标识符的作用域。
②. 命名空间使用语法
1)创建命名空间
#include <iostream>
using namespace std;
namespace A {
int a = 10;
}
namespace B {
int a = 20;
}
int main(int argc, char *argv[])
{
// 定义局部变量a
int a = 30;
cout << "A::a = " << A::a << endl; // 10
cout << "B::a = " << B::a << endl; // 20
cout << "局部变量a = " << a << endl; // 30
return 0;
}
2)命名空间只能全局范围内定义(以下错误写法)
#include <iostream>
using namespace std;
int main(int argc, char *argv[])
{
namespace A {
int a = 10;
}
namespace B {
int a = 20;
}
return 0;
}
3)命名空间可嵌套命名空间
#include <iostream>
using namespace std;
namespace A {
int a = 10;
namespace C {
int a = 40;
}
}
namespace B {
int a = 20;
}
int main(int argc, char *argv[])
{
// 定义局部变量a
int a = 30;
cout << "A::a = " << A::a << endl; // 10
cout << "A::C::a = " << A::C::a << endl; // 40
cout << "B::a = " << B::a << endl; // 20
cout << "局部变量a = " << a << endl; // 30
return 0;
}
4)命名空间是开放的,即可以随时把新的成员加入已有的命名空间中
#include <iostream>
using namespace std;
namespace A {
int a = 10;
}
namespace A {
void func()
{
cout << "namespace A is here ... ..." << endl;
}
}
int main(int argc, char *argv[])
{
cout << "A::a = " << A::a << endl; // 10
A::func(); // namespace A is here ... ...
return 0;
}
5)声明和实现可分离
#include <iostream>
using namespace std;
namespace MySpace {
void func1();
void func2(int param);
}
void MySpace::func1(){
cout << "MySpace::func1" << endl;
}
void MySpace::func2(int param){
cout << "MySpace::func2:" << param << endl;
}
int main(int argc, char *argv[])
{
MySpace::func1(); // MySpace::func1
MySpace::func2(10); // MySpace::func2:10
return 0;
}
6)无名命名空间
意味着命名空间中的标识符只能在本文件内访问,相当于这个标识符加上了static,使其可以作为内部链接
#include <iostream>
using namespace std;
namespace{
int a = 10;
void func1(int param){
cout << "func1:" << param << endl;
}
}
int main(int argc, char *argv[])
{
cout << "a:" << a << endl; // 10
func1(20); // func1:20
return 0;
}
7)命名空间的别名
#include <iostream>
using namespace std;
namespace veryLongNamespace{
int a = 10;
void func1(int param){
cout << "func1:" << param << endl;
}
}
int main(int argc, char *argv[])
{
namespace VLNS = veryLongNamespace;
cout << "veryLongNamespace:a:" << veryLongNamespace::a << endl; // 10
cout << "veryLongNamespace:a:" << VLNS::a << endl; // 10
veryLongNamespace::func1(20); // func1:20
VLNS::func1(20); // func1:20
return 0;
}
③. using声明 命名空间中的成员可用
#include <iostream>
using namespace std;
namespace A{
int paramA = 20;
int paramB = 30;
void funcA(){cout << "this is funcA ... ... " << endl;}
void funcB(){cout << "this is funcB ... ... " << endl;}
}
int main(int argc, char *argv[])
{
// 1. 通过命名空间域运算符(推荐)
cout << A::paramA <<
cout << A::paramB << endl;
A::funcA();
A::funcB();
// 2. using声明成员可用
using A::paramA;
using A::funcA;
cout << paramA << endl;
// cout << paramB << endl; 不可以直接访问
funcA();
// funcB(); 不可以直接访问
// 3. 同名冲突
// int param = 20; 相同作用域注意同名冲突
return 0;
}
using 声明成员碰到函数重载
#include <iostream>
using namespace std;
namespace A{
void func(){cout << "this is void func ... ..." << endl;}
void func(int x){cout << "this is void func(int x) ... ..." << x << endl;}
int func(int x, int y){cout << "this is int func(int x) ... ..." << x+y << endl;}
}
int main(int argc, char *argv[])
{
using A::func;
func();
func(10);
func(10,20);
return 0;
}
如果命名空间包含一组相同名字重载的函数,using声明就声明了这个重载函数的所有集合
④. using声明 整个命名空间可用
#include <iostream>
using namespace std;
namespace A{
int paramA = 20;
int paramB = 30;
void funcA(){cout << "this is funcA ... ..." << endl;}
void funcB(){cout << "this is funcB ... ..." << endl;}
}
int main(int argc, char *argv[])
{
using namespace A;
cout << paramA << endl; // 20
cout << paramB << endl; // 30
funcA();
funcB();
// 不会产生二义性(优先寻找局部变量)
int paramA = 100;
cout << paramA << endl; // 100
return 0;
}
#include <iostream>
using namespace std;
namespace A{
int paramA = 20;
int paramB = 30;
void funcA(){cout << "this is funcA ... ..." << endl;}
void funcB(){cout << "this is funcB ... ..." << endl;}
}
namespace B{
int paramA = 200;
int paramB = 300;
void funcA(){cout << "this is funcA ... ..." << endl;}
void funcB(){cout << "this is funcB ... ..." << endl;}
}
int main(int argc, char *argv[])
{
using namespace A;
using namespace B;
// 二义性产生,不知道调用A还是B的paramA
cout << paramA << endl;
return 0;
}
注意:使用using声明或using编译指令会增加命名冲突的可能性,也就是说,如果有名称空间,并在代码中使用作用域解析预算符,则不会出现二义性
4. struct类型增强
C中定义结构体变量需要加上struct关键字,C++不需要。C中的结构体只能定义成员变量,不能定义成员函数。C++即可以定义成员变量,也可以定义成员函数。
①. 结构体中既可以定义成员变量,也可以定义成员函数
struct Student
{
string mName;
int mAge;
void setName(string name){mName = name;}
void setAge(int age){mAge = age;}
void showStudent(){
cout << "Name:" << mName << " " << "Age:" << mAge << endl;
}
};
②. C++中定义结构体变量不需要加struct关键字
#include <iostream>
using namespace std;
struct Student
{
string mName;
int mAge;
void setName(string name){mName = name;}
void setAge(int age){mAge = age;}
void showStudent(){
cout << "Name:" << mName << " " << "Age:" << mAge << endl;
}
};
int main(int argc, char *argv[])
{
Student student;
student.setName("Bob");
student.setAge(20);
student.showStudent(); // Name:Bob Age:20
return 0;
}
5. bool类型关键字
标准C++的bool类型有两种内建的常量true(转换为整数1)和false(转换为整数0)表示状态。这三个名字都是关键字。bool类型只有两个值,true(1值)和false(0值),bool类型占1字节大小,给bool类型赋值时,非0值会自动转换为true(1),0值会自动转换为false(0)
#include <iostream>
#include<windows.h>
using namespace std;
int main(int argc, char *argv[])
{
cout << sizeof(true) << endl; // 1,bool类型占一个字节大小
cout << sizeof(false) << endl; // 1
bool flag1 = true;
cout << flag1 << endl; // 1
flag1 = 10;
cout << flag1 << endl; // 1,非0自动转换为true(1)
bool flag2 = false;
cout << flag2 << endl; // 0
return 0;
}
6. reference 引用
在C/C++中指针的作用基本都是一样的,但是C++增加了另外一种给函数传递地址的途径,这就是按引用传递(pass-by-reference)。变量名实质上是一段连续内存空间的别名,是一个标号(门牌号),程序中通过变量来申请并命名内存空间通过变量的名字可以使用存储空间
对一段连续的内存空间只能取一个别名吗?C++中新增了引用的概念,引用可以作为一个已定义变量的别名。
①. 引用的定义
引用的本质:就是给变量名取个别名
引用的步骤:
1. &别名
2. 给哪个变量取别名,就定义该变量
3. 从上往下整体替换
②. 普通变量的引用
#include <iostream>
#include<windows.h>
using namespace std;
int main(int argc, char *argv[])
{
int a =10;
// 需求:给变量a 取个别名b
// 定义的时候 &修饰变量为引用 b就是a的别名(引用)
// 系统不会为引用开辟空间
int &b = a; // 引用必须初始化
// a和b代表同一空间内容
cout << a << endl; // 10
cout << b << endl; // 10
cout << &a << endl; // 0x61fe88
cout << &b << endl; // 0x61fe88
// 操作b等于操作a
b = 100;
cout << a << endl; // 100
cout << b << endl; // 100
return 0;
}
③. 数组的引用
#include <iostream>
#include<windows.h>
using namespace std;
int main(int argc, char *argv[])
{
int arr[5] = {1,2,3,4,5};
int n = sizeof(arr)/sizeof(arr[0]);
int (&myArr)[5] = arr;
for(int i=0;i<n;i++)
{
cout << myArr[i] << " ";
}
cout << endl;
return 0;
}
④. 指针变量的引用
#include <iostream>
#include<windows.h>
using namespace std;
int main(int argc, char *argv[])
{
int num = 10;
int *p = #
int *(&myp) = p;
cout << *p << endl; // 10
cout << *myp << endl; // 10
return 0;
}
⑤. 函数的引用
#include <iostream>
#include<windows.h>
using namespace std;
void func()
{
cout << "this is func ... ..." << endl;
}
void (&myfunc)() = func;
int main(int argc, char *argv[])
{
func(); // this is func ... ...
myfunc(); // this is func ... ...
return 0;
}
⑥. 引用作为函数的参数
函数内部可以通过引用操作外部变量
#include <iostream>
#include<windows.h>
using namespace std;
// 指针实现交换函数
void swap1(int *p1, int *p2)
{
int temp = *p1;
*p1 = *p2;
*p2 = temp;
}
// 引用实现交换函数
void swap2(int &x, int &y)
{
int temp = x;
x = y;
y = temp;
}
int main(int argc, char *argv[])
{
int a = 10;
int b = 20;
cout << "未交换前:a = " << a << " " << "b = " << b << endl;
swap1(&a,&b);
cout << "经过swap1第一次交换后: a = " << a << " " << "b = " << b << endl;
swap2(a,b);
cout << "经过swap2第二次交换后: a = " << a << " " << "b = " << b << endl;
return 0;
}
引用的语法更清楚简单:
1)函数调用时传递的实参不必加 & 符
2)在被调函数中不必在参数前面加 * 符,引用作为其他变量的别名而存在,因此在一些场合可以替代指针。
3)C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错
⑦. 引用作为函数的返回值
1)不要返回普通局部变量的引用
#include <iostream>
#include<windows.h>
using namespace std;
int &getData()
{
int num = 10;
// 不要返回局部变量的引用
return num; // 返回num 函数调用的结果就是num的别名
}
int main(int argc, char *argv[])
{
// b就是num的别名
int &b = getData();
cout << b << endl; // 无输出,num在函数调用完成后已经被释放
return 0;
}
2)返回值类型为引用可以完成链式操作
#include <iostream>
#include<windows.h>
using namespace std;
struct Stu
{
Stu &printStu(Stu &ob, int value)
{
cout << value << " ";
return ob;
}
};
int main(int argc, char *argv[])
{
Stu ob1;
ob1.printStu(ob1, 100).printStu(ob1, 200).printStu(ob1, 300); // 100 200 300
cout << endl;
return 0;
}
⑧. 常引用
1)给常量取别名,不能通过常饮用修改内容
#include <iostream>
#include<windows.h>
using namespace std;
int main(int argc, char *argv[])
{
// int &a = 10; erro
const int &a = 10; // a就是10的别名
// a = 100; erro
cout << a << endl;
return 0;
}
2)常引用作为函数的参数:防止函数内部修改外部的值
#include <iostream>
#include<windows.h>
using namespace std;
void printInt(const int &a)
{
// a = 200; erro
cout << "a = " << a << endl;
}
int main(int argc, char *argv[])
{
int num = 100;
printInt(num); // a = 100
return 0;
}
7. 内联函数
①. 声明内联函数
内联函数必须在定义的时候使用关键字 inline 修饰,不能再声明的时候使用 inline
#include <iostream>
#include<windows.h>
using namespace std;
// 函数声明的时候不能使用inline
int my_add(int x, int y);
int main(int argc, char *argv[])
{
cout << my_add(100,200) << endl;
return 0;
}
// 内联函数在定义的时候使用inline
inline int my_add(int x, int y)
{
return x+y;
}
内联函数:在编译阶段,将内联函数中的函数体替换函数调用出。避免函数调用时的开销。
②. 宏函数和内联函数的区别
宏函数和内联函数都会在适当的位置进行展开,避免函数调用开销
宏函数的参数没有类型,不能保证参数的完整性;内联函数的参数有类型,能保证参数的完整性
宏函数在预处理阶段展开;内联函数在编译阶段展开
宏函数没有作用域的限制,不能作为命名空间、结构体、类的成员;内联函数有作用域的限制,能作为命名空间、结构体、类的成员
③. 内联函数的注意事项
在内联函数定义的时候加 inline 修饰
类中的成员函数默认都是内联函数(不加 inline 也是内联函数)
有时候加上 inline 也不一定是内联函数(内联函数的条件)
1)不能存在任何形式的循环语句
2)不能存在过多的条件判断语句
3)函数体不能过于庞大
4)不能对函数取地址
有时候不加 inline 修饰也可能是内联函数
内不内联由编译器决定
8. 函数重载
①. 函数重载的概述
能使名字方便使用,是任何程序设计语言的一个重要特征
现实生活中经常会碰到一些字在不同的场景下具有不同的意思,我们需要根据上下文判断其意思。同样一个字在不同的场景下具有不同的含义。在C++中也有一种类似的现象出现,同一个函数名在不同的场景下具有不同的含义。
函数重载是C++的多态的特性(静态多态)
函数重载:用同一个函数名代表不同的函数功能
②. 函数重载的条件
同一作用域,函数的参数类型、个数、顺序不同,都可以重载。(返回值类型不能作为重载的条件)
#include <iostream>
#include<windows.h>
using namespace std;
void printFun(int a)
{
cout << "int" << endl;
}
void printFun(int a, char b)
{
cout << "int char" << endl;
}
void printFun(char a, int b)
{
cout << "char int" << endl;
}
void printFun(char a)
{
cout << "char" << endl;
}
int main(int argc, char *argv[])
{
printFun(10); // int
printFun(10,'a'); // int char
printFun('a',10); // char int
printFun('a'); // char
return 0;
}
思考:为什么函数返回值不能作为重载条件呢?
当编译器能从上下文中确认唯一的函数时,如 int ret = func() ,这个当时是没有问题的。然而,我们在编写程序过程中可以忽略他的返回值。那么这个时候,一个函数为 void func(int x) ; 另一个函数为 int func(int x) ;当我们直接调用 func(10) ,这个时候编译器就不能确定调用哪个函数。所以在C++中禁止使用返回值作为重载的条件
③. 函数重载的底层实现原理
void func(){}
void func(int x){}
void func(int x, char y){}
以上三个函数在linux下生成的编译之后的函数名为
_z4funcv // v 代表void,无参数
_z4funci // i 代表参数为int类型
_z4funcic // i 代表第一个参数为int类型,第二个参数为char类型
不同编译器可能会产生不同的内部名,此处仅举例说明
9. 函数的默认参数
C++在声明函数原型时可以为一个或者多个参数指定默认(缺省)参数值,当函数调用的时候没有指定这个值,编译器会自动用默认值代替
#include <iostream>
#include<windows.h>
using namespace std;
void Func1(int a=10, int b=20)
{
cout << "a + b = " << a+b << endl;
}
// 注意点:
// 1. 形参b设置默认参数值,那么后面位置的形参c也需要设置默认参数值
void Func2(int a, int b=10, int c=20){}
// 2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能在设置默认参数
void Func3(int a=0, int b=0);
void Func3(int a, int b){}
int main(int argc, char *argv[])
{
// 1. 如果没有传参数,那么使用默认参数
Func1(); // 30
// 2. 如果传一个参数,那么第二个参数使用默认参数
Func1(100); // 120
// 3. 如果传入两个参数,那么两个参数都是传入的参数
Func1(100,200); // 300
return 0;
}
默认参数和函数重载同时出现一定要注意二义性
#include <iostream>
#include<windows.h>
using namespace std;
void Func(int x)
{
cout << "A:int x = " << x << endl;
}
void Func(int x, int y=10)
{
cout << "B:int x = " << x << " " << "y = " << y << endl;
}
int main(int argc, char *argv[])
{
Func(10,20); // ok
// Func(10); // erro 产生二义性
return 0;
}
10. 占位参数
C++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数体内部无法使用占位参数
#include <iostream>
#include<windows.h>
using namespace std;
void Func1(int a, int b, int)
{
// 函数内部无法使用占位参数
cout << "a + b = " << a+b << endl;
}
// 占位参数也可以设置默认值
void Func2(int a, int b, int = 10)
{
// 函数内部依然无法使用占位参数
cout << "a + b = " << a+b << endl;
}
int main(int argc, char *argv[])
{
// 错误调用,占位参数也是参数,必须传参数
// Func1(10,20)
// 正确调用
Func1(10,20,30);
// 正确调用
Func2(10,20);
// 正确调用
Func2(10,20,30);
return 0;
}
什么时候用,在后面操作符重载的后置++要用到这个
11. extern “C”浅析
以下在Linux下测试:C函数:void MyFunc( ){ },被编译成函数:MyFunc
C++函数:void MyFunc( ){ },被编译成函数:_Z6MyFuncv
通过这个测试,由于C++中需要支持函数重载,所以C和C++中对同一个函数经过编译后生成的函数名时不相同的,这就导致了一个问题,如果C++中调用一个使用C语言编写模块中的某个函数,那么C++是根据C++的名称修饰方式来查找冰链接这个函数,那么就会发生链接错误,以上例,C++中调用MyFunc函数,在链接阶段会去找_Z6MyFuncv,结果是没有找到的,因为这个MyFunc函数是C语言编写的,生成的符号是MyFunc。那么如果想在C++调用C的函数怎么办?extern "C" 的主要作用就是为了实现C++代码能够调用其他C语言代码。加上extern “C”后,这部分代码编译器按C语言方式进行编译和链接,而不是按C++的方式。
fun.h
#ifndef MYMODULE_H
#define MYMODULE_H
#include<stdio.h>
// 第一部分
#if __cplusplus
extern "C"{
#endif
// 第二部分
extern void func1();
extern int func2(int a, int b);
// 第三部分
#if __cplusplus
}
#endif
#endif
fun.c
#include <stdio.h>
#include "fun.h"
void func1()
{
printf("hello world!");
}
int func2(int a, int b)
{
return a+b;
}
main.cpp
# include <iostream>
# include "fun.h"
using namespace std;
int main()
{
func1();
cout << func2(10,20) << endl;
return 0;
}