C++笔记 day27 类 函数重载 封装

71 阅读3分钟

设计一个类,求圆的周长

  1. class +类名 (成员变量,成员函数)

  2. 权限,公共权限 public: 在public: 下的都是公共的

  3. 设计成员属性 半径: int m_R

  4. 设计成员函数

    获取圆周长 int calculate2()

    获取圆半径 int getR()

    设置圆半径 void setR()

5.通过类创建对象过程 称为实例化对象 Circle C1;

示例:圆类,计算圆的周长

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
using namespace std;

//设计一个类,求圆的周长
const double PI = 3.14;
//class + 类名
//周长的公式 2 * pi * m_R
class Circle
{
public://公共权限

	//类中的函数 称为 成员函数 或 成员方法
	//求圆周长
	double calculate2()
	{
		return 2 * PI * m_R;
	}
	//设置半径
	void setR(int r)
	{
		m_R = r;
	}
	//获取半径
	int getR()
	{
		return m_R;
	}
	//类中的变量 称为成员变量或成员属性
	//半径
	int m_R;
};

void test01() 
{
	Circle c1;//通过类 创建一个对象, 实例化对象
	//给c1 半径赋值
	c1.m_R = 10;

	//求c1圆周长
	cout << "圆的周长为:" << c1.calculate2() <<"圆的半径为"<<c1.getR () << endl;
}

示例:学生类,姓名和学号,可以赋值和展示

// 设计一个学生类,属性有姓名和学号,可以给姓名和学号赋值,可以显示学生姓名和学号
class Student 
{
public:
	//设置姓名
	void setName(string name)
	{
		m_Name = name;
	}
	//设置学号
	void setId(int id) 
	{
		m_Id = id;
	}
	//显示学生信息
	void showStudent() 
	{
		cout << "学生的姓名为:" << m_Name << "  " << "学号为:" << m_Id << endl;
	}

	//属性:
	//姓名
	string m_Name;
	//学号
	int m_Id;
};

void test02()
{
	Student s1;
	s1.m_Name = "张三";
	s1.m_Id = 10086;
	cout << "学生的姓名为:" << s1.m_Name <<"  " << "学号为:" << s1.m_Id<<endl;

	Student s2;
	s2.setName("李四");
	s2.setId(10010);
	s2.showStudent();
}

内联函数

内联函数的引出

宏的缺陷

  1. 必须要加括号保证运算完整
  2. 即使加了括号,有些运算依然与预期不符

内联函数的基本概念

在c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。

在普通函数(非成员函数)函数前面加上inline关键字使之成为内联函数。

但是必须注意必须**函数体****声明**结合在一起,否则编译器将它作为普通函数来对待。

image.png

内联函数的好处

避免了宏函数的缺陷同时可以实现宏函数的功能,以空间换时间。

类内部的成员函数在函数前都隐式的加了 inline 关键字,自动成为内联函数。

C++的内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译

1. 不能存在任何形式的循环语句
2. 不能存在过多的条件判断语句
3. 函数体不能过于庞大
4. 不能对函数进行取址操作

内联函数仅仅只是给编译器一个建议,编译器不一定会接收这种建议,如果你没有将函数声明为内联函数,编译器也可能会将函数做内联编译,一个好的编译器将会内联小的,简单的函数。

函数的默认参数和占位参数

默认参数,可以给函数的形参添加默认的值

如果有一个位置有了默认参数,那么从这个位置其后面的都要有默认参数

// 默认参数 语法 形参 类型 变量 = 默认值
// 注意事项,如果有一个位置有了默认参数,那么从这个位置起,从左到右都必须有默认值
// 注意事项,函数的声明和实现,只能有一个提供默认参数,不可以同时加默认参数
int func(int a = 10, int b = 10) 
{
	return a + b;
}

函数的声明和实现,只能有一个提供默认参数,不可以同时加默认参数

占位参数也可以有默认的值 void func(int a,int =1)

// 占位参数,只写一个类型进行占位,调用时候必须要传入占位值
void func2(int a, int) 
{
	
}

函数重载

函数重载的基本语法

C++的语法是可以出现函数名称相同的函数的。

函数重载的条件

  1. 在同一个作用域
  2. 函数名称相同
  3. 参数个数、类型和顺序不同
  4. 加const和不加const的引用

注意:返回类型不可以作为函数重载的条件

函数重载要避免二义性

如函数重载中引用的两个版本,加const和不加const的引用也可以作为重载条件

函数重载碰到默认参数的情况

示例

#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 myFunc(int& a)  // int & a =10 ,语法错误
{
	cout << "myFunc(int & a)调用" << endl;
}

void myFunc(const int& a) // const int & a =10
{
	cout << "myFunc(const int & a)调用" << endl;
}

void test01() 
{
	int a = 10;
	//myFunc(a); 调用 int & a 版本
	myFunc(10); //调用 const int & a版本,语法不出错
	
}

//函数重载碰到默认参数,避免二义性出现


int main()
{
	test01();


	system("pause");
	return EXIT_SUCCESS;
}

externC浅析

在C++中有函数重载,会修饰函数名,但是show()是C语言文件中的函数,因此直接用头文件包含链接失败

解决方法:

  1. 可以在C++文件中添加

     extern"C" void show(); //使用这个语句,相当于告诉编译器用C语言方式去链接 对应的函数
    
  2. 可以在C的头文件中添加“上三行”和“最后三行”内容

#ifdef __cplusplus
extern "C" {
#endif

#pragma once
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>


	void show();

#ifdef __cplusplus
 }
#endif

C语言和C++的封装

C语言中属性和行为分离

struct Person 
{
	char name[64];
	int age;
};

void PersonEat(struct Person* p) 
{
	printf("%s在吃人饭\n", p->name);
}


void test01() 
{
	struct Person p;
	strcpy(p.name, "张三");
	p.age = 18;

	PersonEat(&p);
}

C++封装理念

1. 将属性和行为作为一个整体来表现生活中的事务

    
struct Person 
{
	int age;
	char name[64];

	void PersonEat() 
	{
		printf("%s在吃人饭\n", name);
	}
};

struct Dog
{
	int age;
	char name[64];

	void DogEat()
	{
		printf("%s在吃狗粮\n", name);
	}
};

2. 将属性和行为加以权限控制 
    structclass的区别,class的默认权限是私有的,struct的默认权限是公共的
    访问权限:
        public 公共权限    成员在类内类外都可以访问 
        private 私有权限   成员在类内 可以访问 类外 不可以访问  子类不可以访问父类内容   
        protected 保护权限 成员在类内可以访问  类外 不可以访问  子类可以访问父类内容
struct Person2 
{
public:	
	string m_name;//公共权限
protected:
	string m_car;//保护权限
private:
	int pwd;//私有权限
public:
	void func() 
	{
		m_name = "张三";
		m_car = "拖拉机";
		pwd = 12346;
	}
};

void test01() 
{
	Person2 p1;
	p1.m_name = "张三"; //公共权限
	p1.m_car = "拉斯莱斯"; //报错,保护权限类外访问不到
	p1.pwd = 123; //报错,私有权限 类外访问不到
}

尽量将成员的属性设置为私有

  1. 将成员属性都设置为私有的好处,在于自己可以控制读写权限,提供相应接口。
  2. 可以对设置的内容进行有效性的验证

示例

#define _CRT_SECURE_NO_WARNINGS
#include<iostream>
#include <string>
using namespace std;

class Person 
{
public: //对外就属性提供相关接口
	//设置姓名
	void setName(string name) 
	{
		m_Name = name;
	}
	//获取姓名
	string getName() 
	{
		return m_Name;
	}
	//获取年龄
	int getAge() 
	{
		return m_Age;
	}
	//设置情人
	void setLover(string Lover) 
	{
		m_Lover = Lover;
	}
	//获取资产
	void setMony(int mony) 
	{
		if (mony < 0 || mony >500) //验证数据有效性
		{
			cout << "钱太多了" << endl;
			return;
		}
		m_Mony = mony;
	}
	int getMony()
	{
		return m_Mony;
	}
private:
	string m_Name;			//姓名 可读可写
	int m_Age = 18;			//年龄 只读
	string m_Lover;			// 情人 只写
	int m_Mony;				// 资产 可读 可写(0~500之间)
};

void test01() 
{
	Person p;
	//可以将char * 隐式类型转换为 string
	p.setName("张三");
	cout << p.getName() << endl;

	//获取年龄
	cout << p.getAge() << endl;

	//设置情人
	p.setLover("Amy"); //是一个只写权限,外部访问不到

	//设置资产
	p.setMony(600);
	cout << p.getMony() << endl;
}

int main()
{

	test01();

	system("pause");
	return EXIT_SUCCESS;
}