C++核心编程 day02 内联函数、函数重载与默认参数、externC、类
1. 内联函数
在C语言中宏函数可以增加程序的运行效率,是一种以空间换时间的做法。但是宏函数是有一些缺陷的,下面的宏的一些缺陷:
- 宏必须要添加括号才能保证运算完整性。
- 即使添加了括号,在一些运算上仍然会出现与预期不符合的情况。
而我们使用函数则不会出现这种情况。下面是一个示例代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
// 宏缺陷1:必须加括号才能保证运算完整
#define MYADD(x,y) ((x)+(y))
void test01()
{
int a = 10;
int b = 20;
int ret = MYADD(a, b) * 10;
cout << "ret = " << ret << endl;
}
// 宏缺陷2:即使加了括号有些运算仍然与预期不符合
#define DOUBLEADD(x) ((x)+(x))
// 普通函数不会出现与逾期不符合的问题
int doubleAdd(int x)
{
return x + x;
}
void test02()
{
int a = 10;
//int ret = DOUBLEADD(++a);
//cout << "ret = " << ret << endl;
cout << "ret = " << doubleAdd(++a) << endl;
}
// 内联函数
// 内联函数声明和实现必须同时加上关键字inline才能算内联函数是
// 内联函数好处:解决宏缺陷,本身是一个函数,带来宏优势,以空间换时间,在适当的时候做展开
inline void func();
inline void func() {}
// 类内部的成员函数在函数前都隐式加上了关键字inline
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
内联函数需要函数的声明和实现同时加上关键字inline
才能算内联函数。内联函数可以解决宏缺陷,本身也是一个函数,可以提高程序的效率,也是以空间来换时间的一种做法。内联函数是在适当的时候按照宏的方式来做展开。在C++中,类内部的成员函数在函数前都由编译器隐式地加上了关键字inline
。内联函数只是给编译器的一个建议,编译器也可能并不会采纳。同样的如果一个函数没有加上inline
关键字,编译器也可能会按照内联函数的方式进行编译。因此我们不用刻意去对函数前面添加inline
关键字。
在以下的情况下编译器可能不会将函数进行内联编译:
- 不能存在任何形式的循环语句。
- 不能存在过多的条件判断语句。
- 函数体不能过于庞大。
- 不能对函数进行取地址操作。
最后一点很好理解,因为一个内联函数按照宏的方式进行展开的话,也就不会产生函数的栈帧,连函数都不存在,所以也不会有所谓的函数的入口地址。
2. 函数重载与默认参数
2.1 函数的默认参数与占位参数
在C++中,可以对函数设置默认参数,其设置方法是函数定义的形参列表中形参 变量=默认值
。当给了函数的实际参数之后,则按照实际参数的形式进行调用,若没有给实际参数,则函数使用的是形参的默认值。需要注意的是如果有一个位置有了默认参数,那么从这个位置开始从左到右都必须要有默认值。在函数的声明和实现之中,只能有一个提供默认参数,不可以同时添加默认参数。
在C++中还提供了占位参数,占位参数只是在形参的位置写一个类型进行占位,调用的时候必须要传入占位值。在占位参数上也可以设置默认参数的。下面是关于默认参数与占位参数的示例代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
// 默认参数 语法 形参 类型 变量 = 默认值
// 注意事项, 如果一个位置有了默认参数,那么从这个位置开始,从左到右都必须有默认值
int func(int a, int b = 10, int c = 20)
{
return a + b + c;
}
void test01()
{
cout << func(20, 10) << endl;
}
// 函数的声明和实现,只能有一个提供默认参数,不可以同时提供默认参数
void func1(int a = 10, int b = 20);
void func1(int a, int b) {}
// 占位参数,只写一个类型进行占位,调用时候必须要传入占位值
void func2(int a, int = 1)
{
}
void test02()
{
func2(10);
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
2.2 函数的重载
在C++中提供了函数重载。函数重载即同一个函数名可以有多个调用的方式。函数重载需要满足一下的条件:
- 在同一个作用域中。
- 函数名必须相同。
- 参数个数、类型、顺序不同。
需要注意的是返回值不同的话是不能作为函数重载的条件的。在C++中,函数重载编译器会对函数名进行修饰,也就是编译后的函数名并不是原来的函数名。
在C++的重载之中,引用也有两个版本。以int
类型为例,分别有void func2(int &a);
和void func2(const int &a);
。第一个引用的重载传递的参数是必须要有合法的内存空间的,所以不支持常数。而第二个是可以支持常数的。当然这两个也可以与void func2(int a);
共同存在,但是编译的时候会出现错误,因为不管是对常数还是变量都可以走这个函数重载。在函数的重载之中,我们必须要避免二义性的出现,特别是函数重载遇到了默认参数。下面给出关于函数重载的示例代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
// 函数重载的条件
// 1. 在同一个作用域
// 2. 函数名称相同
// 3. 参数个数、类型、顺序不同
void func()
{
cout << "func() 调用" << endl;
}
void func(int a)
{
cout << "func(int a) 调用" << endl;
}
void func(double a)
{
cout << "func(double a) 调用" << endl;
}
void func(int a, double b)
{
cout << "func(int a, double b) 调用" << endl;
}
void func(double a, int b)
{
cout << "func(double a, int b) 调用" << endl;
}
// 函数的返回值不可以作为函数重载的条件
void test01()
{
func();
func(2.9, 32);
func(2);
func(3.23);
func(32, 89.2);
}
// 函数重载中引用的两个版本
//void func2(int a)
//{
// cout << "func2(int a) 调用" << endl;
//}
void func2(int &a)
{
cout << "func2(int &a) 调用" << endl;
}
void func2(const int &a)
{
cout << "func2(const int &a) 调用" << endl;
}
void test02()
{
int a = 10;
func2(a);
func2(10); // 需要避免二义性的出现
}
// 函数重载碰到默认参数,注意避免二义性的出现
void fun3(int a, int b = 20)
{
}
void func3(int a)
{
}
void test03()
{
//func2(10); // 出现二义性
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
3. extern “C”
若现在在C++ 中写了一个函数如下:
#include "test.h"
void show()
{
printf("Hello World\n");
}
此时我们在C++程序中调用该C语言的代码。此时如果正常的运行该程序,则程序在链接的时候会出现错误。原因是C++中有函数的重载,编译器会对函数名进行修饰。如下面的C++程序要调用show
函数则需要按照以下代码书写:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
// 告诉编译器,show函数用C语言方式做链接
extern "C" void show();
int main()
{
show();
system("pause");
return EXIT_SUCCESS;
}
这里的extern "C" void show();
就是告诉编译器这个函数需要用C语言的方式做链接。当然我们也可以在该函数声明的头文件中写一下的宏定义代码:
#ifndef __TEST_H__
#define __TEST_H__
#ifdef __cplusplus
extern "C" {
#endif
#include <stdio.h>
void show();
#ifdef __cplusplus
}
#endif
#endif
其中__cplusplus
表示C++的宏。
4. 类
4.1 C语言下的封装
在C语言中,我们常用struct
对一些数据进行封装。如下述代码:
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
struct Person
{
char name[64];
unsigned int age;
};
void personEat(struct Person *p)
{
printf("%s 在吃人饭\n", p->name);
}
void test01()
{
struct Person p;
strcpy(p.name, "米开兰奇罗");
p.age = 20;
personEat(&p);
}
struct Dog
{
char name[64];
unsigned int age;
};
void dogEat(struct Dog *d)
{
printf("%s 在吃狗饭\n", d->name);
}
void test02()
{
struct Dog d;
strcpy(d.name, "小咖");
d.age = 28;
dogEat(&d);
struct Person p;
strcpy(p.name, "小叶");
p.age = 42;
dogEat(&p);
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
在C语言中我们封装的时候成员属性和成员方法是分离开来的。这就导致了如果定义的结构体的成员是一样的话,则同一个方法可以让不同的结构体进行调用产生一些问题。比如人可以调用狗的方法,狗也可以调用人的方法。因此我们需要将成员属性和成员方法进行分离,而这就是C++中的类。
4.2 C++下的封装
在C++中,我们将对象的成员属性和成员方法封装在了一起。如上面C语言中的程序在C++中就可以写成这样的代码:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
struct Person
{
public:
char name[64];
unsigned int age;
void eat()
{
cout << name << "在吃人饭" << endl;
}
};
struct Dog
{
public:
char name[64];
unsigned int age;
void eat()
{
cout << name << "在吃狗饭" << endl;
}
};
// C++封装理念:将属性和行为做为一个整体,来表现生活中的事物
// 第二次理念: 将属性和行为加以权限控制
void test01()
{
struct Person p;
strcpy(p.name, "贝多芬");
p.eat();
struct Dog d;
strcpy(d.name, "小咖");
d.eat();
}
// struct 和class的区别
// class默认权限是私有权限,struct默认权限是公共权限
// 访问权限
// public 公共权限 成员 类内 类外都可以访问
// private 私有权限 成员 类内可以访问,类外不可以访问 儿子不可以访问父亲的私有权限的成员
// protected 保护权限 成员 类内可以访问,类外不可以访问 儿子可以访问父亲的保护权限的成员
class Person2
{
public:
string _name;
protected:
string _car;
private:
int _pwd;
public:
void func()
{
_name = "米开兰奇罗";
_car = "劳斯莱斯";
_pwd = 123456;
}
};
void test02()
{
Person2 p;
p._name = "贝多芬"; // 公共权限 类外可以访问
//p._car = "奥迪"; // 保护权限 类外访问不到
//p._pwd = 15115; // 私有权限 类外访问不到
}
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}
在C++中,我们将属性和行为视为一个整体,来表现生活中的事物,这样可以对成员属性和成员方法加以权限控制。
在类与结构体中,这两者的默认权限是不一样的。首先结构体的默认权限是公共权限,而类的默认权限是私有权限。访问权限有三个,首先public
是公共属性,其类中的成员在类内以及类外都可以访问。其次是private
,这是私有属性,类中的成员在类内可以访问,类外不可以访问,但是在继承中儿子是不可以访问父亲中私有属性。最后是protected
,保护权限,类中的成员是可以访问的,类外不可以访问,在类的继承之中儿子是可以访问父亲中保护权限的成员。
4.3 类的定义
类的定义为
class 类名
{
权限:
成员属性;
成员方法;
};
比如下面代码就定义了一个类:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
#include <cmath>
// 设计一个类,求圆的周长
// class + 类名
const double PI = 3.1415926;
class Circle
{
public:
// 求圆的周长
double caculatePerimeter()
{
return 2 * PI * _r;
}
// 设置半径
void setR(int r)
{
_r = r;
}
// 获取半径
int getR()
{
return _r;
}
int _r;
};
void test01()
{
Circle c1; // 通过类实例化一个对象
// 给半径赋值
//c1._r = 10;
c1.setR(10);
cout << "圆的半径为: " << c1.getR() << endl;
cout << "圆的周长为: " << c1.caculatePerimeter() << endl;
}
// 设计一个学生类,属性要有学号和姓名, 可以给姓名和学号赋值, 可以显示学生的姓名和学号
class Student
{
public:
// 设置姓名
void setName(string name)
{
_name = name;
}
// 设置学号
void setSno(string sno)
{
_sno = sno;
}
// 显示学生的信息
void showStudent()
{
cout << "学生姓名: " << _name << endl;
cout << "学生学号: " << _sno << endl;
}
string _name;
string _sno;
};
void test02()
{
Student s1;
s1._name = "米开兰奇罗";
s1._sno = "1102101201";
cout << "姓名: " << s1._name << " 学号: " << s1._sno << endl;
Student s2;
s2.setName("丹尼斯里奇");
s2.setSno("231512132");
s2.showStudent();
Student s3;
s3.setName("贝多芬");
s3.setSno("2115123121");
s3.showStudent();
}
int main()
{
test01();
test02();
system("pause");
return EXIT_SUCCESS;
}
但是在类中,我们一般将成员属性都设置为私有的权限,这样设置的好处是可以控制读写权限,也可以对内容增加有效性验证。比如下面的Person
类就年龄做了一个有效性的验证,代码如下:
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
using namespace std;
#include <string>
class Person
{
private:
string _name; // 可读可写
unsigned int _age = 18; // 可读不可写
string _lover; // 可写不可读
public:
// 设置姓名
void setName(string name)
{
_name = name;
}
// 获取姓名
string getName()
{
return _name;
}
// 设置年龄
void setAge(int age)
{
if (age < 0)
{
cout << "越活越回去了吧!" << endl;
return;
}
else if (age > 150)
{
cout << "您可真是一个老妖精" << endl;
return;
}
_age = age;
}
// 获取年龄
unsigned int getAge()
{
return _age;
}
// 设置情人
void setLover(string lover)
{
_lover = lover;
}
};
void test01()
{
Person p;
// 可以将char *隐式类型转换为string
p.setName("达芬奇");
cout << "姓名: " << p.getName() << endl;
// 设置年龄
p.setAge(-200);
cout << "年龄: " << p.getAge() << endl;
// 设置情人
p.setLover("小叶");
}
// 将成员属性都设置为私有的好处:自己可以控制读写权限
// 可以对内容增加有效性验证
int main()
{
test01();
system("pause");
return EXIT_SUCCESS;
}