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;
}

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值