目录
1.宏函数
2.内联函数
3.函数的默认参数
4.函数的占位参数
5.函数的重载
6.C和C++混合编程
6.1 C语言文件:MyModule01.c
6.2 C语言文件对应的头文件:MyModule01.h
7.类和对象
8.课堂练习
内容
#include <iostream>
using namespace std;
/****************************************************************************************************
* 18.宏函数:C/C++的重要特征是效率,而C++的效率>C。在C中常把一些短并且执行频繁的计算写成宏,而不是函数,理由是为了执
* 行效率,宏可以避免函数调用的开销,这些都由预处理来完成。但是在C++出现之后,使用预处理宏函数会出现两个问题:
* ① 宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误,C中也会出现的问题。
* ② 预处理器不允许访问类的成员,即预处理器宏不能用作类的成员函数,C++中特有的问题。
* 与内联函数相比宏函数还存在以下问题:
* ① 不具备完整性;
* ② 当传入参数中有其他运算时,宏函数的执行,会夹带着调用函数中的运算如++运算进行执行;
* ③ 没有作用域的概念,无法作为一个类的成员函数,也就是说预定义宏没有办法表示类的范围。
* 19.内联函数(inline function):为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里访问自如,
* C++引入了内联函数。它继承宏函数的效率(既没有函数调用时开销,又可以像普通函数那样,进行参数、返回值类型的安全检查,
* 还可以作为成员函数。)
* ① 在C++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为;
* ② 内联函数和预定义宏函数的唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销,所
* 以要尽量使用内联函数而非宏;
* ③ 在普通函数(非成员函数)函数前面加上 inline 关键字即为内联函数。注意:函数体和声明必须结合在一起,否则编译器会
* 将它作为普通函数来对待:
* inline void func(int a); // 以上写法没有任何效果,仅仅是声明函数
* inline int func(int a){return ++;} // 内联函数的正确写法
* ④ 在类内部定义内联函数时,inline修饰并不是必须的,因为任何在类内部定义的函数自动成为内联函数。
* 注意: 编译器将会检查函数参数列表使用是否正确,并返回值(进行必要的转换)。这些事预处理器无法完成的。内联函数的确占
* 用空间,但是内联函数相对于普通函数的优势只是省去了函数调用时候的压栈,跳转,返回的开销。我们可以理解为内联函数是以空间
* 换时间。但是C++内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译:
* ① 不能存在任何形式的循环语句;
* ② 不能存在过多的条件判断语句;
* ③ 函数体不能过于庞大;
* ④ 不能对函数进行取址操作。
****************************************************************************************************/
// 宏函数的缺陷一:不具备完整性
#define ADD1(x, y) x+y // 宏函数,实现两个变量的相加
#define ADD2(x, y) (x+y) // 宏函数,实现两个变量的相加,宏函数不具完整性的解决办法
inline int add(int x, int y){return x+y;} // 内联函数实现两个变量的相加
void test12()
{
int ret1 = ADD1(10, 20) * 10; //希望结果是 300
int ret2 = ADD2(10, 20) * 10; //希望结果是 300
int ret3 = add(10, 20) * 10; //希望结果是 300
cout << "ret1:" << ret1 << endl; //但是宏函数结果为210,错误,10+20*10=210
cout << "ret2:" << ret2 << endl; //宏函数不具完整性的解决办法:结果为300,正确,(10+20)*10=300
cout << "ret3:" << ret3 << endl; //内联函数结果为300,正确,(10+20)*10=300
}
// 宏函数的缺陷二:用小括号是为了保证完整性,但是又出现了宏函数中还执行了调用函数中的运算如++运算
#define COMPARE(x, y)((x)<(y)?(x):(y)) // 宏函数,实现两个变量的比较大小,并返回较小的值
inline int compare1(int x, int y){return x<y?x:y;} // 内联函数实现两个变量的比较大小,并返回较小的值
int compare2(int x, int y){return x<y?x:y;} // 普通函数实现两个变量的比较大小,并返回较小的值
void test13()
{
int a = 1;
int b = 3;
// 比较++a和b的大小,取较小的值,我们想得到的结果是2
// cout << "COMPARE(++a, b): " << COMPARE(++a, b) << endl;
// 3×,因为传入值为(++1,3),宏函数中(++1=2)<(y)?(++2=3):(y)所以返回值为3
// cout << "compare1(++a, b): " << compare1(++a, b) << endl;
// 2√,只在传参前执行++,所以直接传入(2, 3)进行比较
cout << "compare2(++a, b): " << compare2(++a, b) << endl;
// 2√,只在传参前执行++,所以直接传入(2, 3)进行比较
}
/****************************************************************************************************
* 20.函数的默认参数:C++在声明函数原型的时可为一个或者多个参数指定默认(缺省)的参数值,当函数调用的时候如果没有指定这
* 个值,编译器会自动用默认值代替。
* ① 如果没有传参数,那么使用默认参数;
* ② 如果传一个参数,那么第二个参数使用默认参数;
* ③ 如果传入两个参数,那么两个参数都使用我们传入的参数
* ④ 函数的默认参数从左向右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须设置默认参数;
* ⑤ 如果函数声明和函数定义分开写,函数声明和函数定义可以同时设置默认参数,但是只有声明处的默认参数起作用,所以建
* 议在函数声明中设置缺省值,不要在函数定义处设置默认值。
****************************************************************************************************/
void TestFunc01(int a = 10, int b = 20){cout << "a + b = " << a + b << endl;}
//1. 形参 b 设置默认参数值,那么后面位置的形参 c 也需要设置默认参数
void TestFunc02(int a,int b = 10,int c = 10){ cout << a << b << c << endl;}
//2. 如果函数声明和函数定义分开,函数声明设置了默认参数,函数定义不能再设置默认参数
void TestFunc03(int a = 0,int b = 0); // 函数的声明
void TestFunc03(int a, int b){cout << "a + b = " << a + b << endl;} // 函数的定义
void test14(){
//1. 没有传参数,那么使用默认参数
TestFunc01();
//2. 传一个参数,那么第二个参数使用默认参数
TestFunc01(100);
//3. 传入两个参数,那么两个参数都使用我们传入的参数
TestFunc01(100, 200);
}
/****************************************************************************************************
* 21.函数的占位参数:C++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名声明。一般情况下,在函数
* 体内部无法使用占位参数,但是又必须传入参数。(了解内容)什么时候用该功能,在后面我们要讲的操作符重载的后置++要用到。
****************************************************************************************************/
void TestFunc04(int a,int b,int){
//函数内部无法使用占位参数
cout << "a + b = " << a + b << endl;
}
//占位参数也可以设置默认值
void TestFunc05(int a, int b, int = 20){
//函数内部依旧无法使用占位参数
cout << "a + b = " << a + b << endl;
}
void test15(){
//错误调用,占位参数也是参数,必须传参数
//TestFunc04(10,20);
//正确调用
TestFunc04(10,20,30);
//正确调用
TestFunc05(10,20);
//正确调用
TestFunc05(10, 20, 30);
}
/****************************************************************************************************
* 22.函数的重载(overload):同一个函数名在不同场景下可以具有不同的含义。 在C语言中,函数名必须是唯一的,程序中不允
* 许出现同名的函数。在C++中是允许出现同名的函数,这种现象称为函数重载。目的就是为了方便的使用函数名。
* ① 实现函数重载的条件:同一个作用域、参数个数不同、参数类型不同、参数顺序不同;
* ② 返回值不作为函数重载依据,所以参数相同,但返回值类型不同是不被允许的;
****************************************************************************************************/
// 1. 函数重载条件,如命名空间E中的5个函数是不同的函数。
namespace E{
void MyFunc(){ cout << "无参数!" << endl; }
void MyFunc(int a){ cout << "a: " << a << endl; }
void MyFunc(string b){ cout << "b: " << b << endl; }
void MyFunc(int a, string b){ cout << "a: " << a << " b:" << b << endl;}
void MyFunc(string b, int a){cout << "a: " << a << " b:" << b << endl;}
}
// 2. 返回值不作为函数重载依据,所以参数相同,但返回值类型不同是不被允许的。
namespace F{
void MyFunc(string b, int a){cout << b << a << endl;}
//int MyFunc(string b, int a){cout << b << a << endl;} //无法重载仅按返回值区分的函数,报错
}
// 注意: 函数重载和默认参数一起使用,需要额外注意二义性问题的产生。
void MyFunc(string b){ cout << "b: " << b << endl;}
//函数重载碰上默认参数
void MyFunc(string b, int a = 10){ cout << "a: " << a << " b:" << b << endl;}
void test16(){
// MyFunc("Hello"); //这时,两个函数都能匹配调用,产生二义性,抛出错误:函数调用不明确
}
/****************************************************************************************************
* 23.C和C++混合编程:
* ① 一般一个C文件就需要匹配一个头文件用于在主函数中进行引用,C文件用于函数的定义,头文件用于函数的声明;
* ② 在Ubuntu系统中的混合编译,如下:
* step1:gcc -c MyModule01.c -o MyModule01.O // 先把C文件编译成二进制文件
* step2:g++ main.cpp MyModule01.o -o main // 借助于上面生成好的二进制文件,编译生成可执行文件main
****************************************************************************************************/
/* test17()和test18()两种不同的运行方法都可以输出结果
#define _CRT_SECURE_NO_WARNINGS
//#include<iostream>
//using namespace std;
#if 0
#if def __cplusplus
extern "C" {
#if 0
void func1();
int func2(int a, int b);
#else
#include"MyModule01.h"
#endif
}
#endif
#else
extern "C" void func1();
extern "C" int func2(int a, int b);
#endif
void test17()
{
func1();
cout << endl << func2(10, 20) << endl;
}
*/
#include"MyModule01.h"
void test18()
{
func1();
cout << endl << func2(10, 20) << endl;
}
/****************************************************************************************************
* 24.类和对象:把事物的属性和行为抽象地表示成计算机语言,用计算机语言来代表这个事物。
* ① 在C语言中,行为和属性是分开的定义的:定义一个结构体用来表示一个对象所包含的静态属性,定义一个函数用来表示一
* 个对象所具有的动态行为,这样我们就可以抽象地表示出一个事物;
* ② 在C++中,结构体不仅可以定义成员变量(用于定于事物静态属性),还可以定义成员函数(用于定义事物动态行为),这
* 样就可以直接用一个结构体表述一种事物,但是C++中提供了更好的方法描述一个事物就是类;
* ③ 类的封装提供一种能够给属性和行为的访问权限加以控制的机制。类的封装包含两个方面的含义:
* i> 属性和变量合成一个整体,即把变量(属性)和函数(操作)合成一个整体,封装在一个类中;
* ii> 给属性和函数增加访问权限,即对变量和函数进行访问权限控制。
* ④ 另外,类的封装还有以下特点:
* i> 在类的内部(作用域范围内),没有访问权限之分,所有成员可以相互访问;
* ii> 在类的外部(作用域范围外),访问权限才有意义:public,private,protected;
* ---------------------------------------------
* | 访问属性 | 属性 | 对象内部 | 对象外部 |
* ---------------------------------------------
* | public | 共有 | 可访问 | 可访问 |
* | private | 私有 | 可访问 | 不可访问 |
* | protected | 保护 | 可访问 | 不可访问 |
* ---------------------------------------------
* iii> 在类的外部,只有public修饰的成员才能被访问,在没有涉及继承与派生时,private和protected是同等级的,
* 外部不允许访问。
* ④ 类和结构体的区别:class 默认访问权限为private,struct 默认访问权限为public;
* ⑤ 类是抽象的概念,系统不会为它开辟空间,但是它占用空间;
* ⑥ 外部如果想访问私有属性,只有通过类内函数进行访问;
* ⑦ 将成员变量设置为private的好处:
* i> 可赋予客户端访问数据的一致性,即外界可访问的内容一样多,而且能访问的私有数据的方法也就这么多,不可修改
* 且对外都一样。
* ii> 可细微划分访问控制。如果我们设置为private,我们可以实现“不准访问”、“只读访问”、“读写访问”,甚至你可
* 以写出“只写访问”。
* ⑧ 注意:类是抽象概念,不应该在类内对成员变量(属性)进行初始化,这样的话类就是去了它本身的意义,就不能被称作类,
* 而应该被称作个体。
****************************************************************************************************/
/*
// 在C语言中描述一个事物,属性和行为分开定义
typedef struct _Person{
char name[64];
int age;
}Person;
typedef struct _Animal{
char name[64];
int age;
int type; //动物种类
}Ainmal;
void PersonEat(Person* person){ printf("%s 在吃人吃的饭!\n",person->name);}
void AnimalEat(Ainmal* animal){ printf("%s 在吃动物吃的饭!\n", animal->name);}
#include<string.h>
void test19(){
Person person;
strcpy(person.name, "小明");
person.age = 30;
AnimalEat(&person); //AnimalEat((Animal *)&person);
}
*/
class Person01{
//人具有的行为(函数)
public:
void Dese(Person01 p)
{
cout << "我有钱:" << p.mMoney << "亿" << endl; // 通过类内函数访问类内保护数据
cout << "我年轻:" << p.mAge << "岁" << endl; // 通过类内函数访问类内私有数据
cout << "我身高:" << p.mTall << "cm" << endl; // p.mTall输出178,传入参数值
cout << "有颜有肉,就爱嘚瑟!" << mTall << endl; // mTall输出也是178,传入的参数值,类内数据并没有访问
} // 通过内部函数访问内部属性
//人的属性(变量)
public:
int mTall = 180; // 多高,可以让外人知道,注意:这里不应该出现类内成员变量的初始化操作。
protected:
int mMoney = 10; // 有多少钱,只能儿子孙子知道
private:
int mAge = 18; // 年龄,不想让外人知道
};
void test20(){
Person01 p; // 类的实例化被称为对象
p.mTall = 178;
// p.mMoney 保护成员外部无法访问
// p.mAge 私有成员外部无法访问
p.Dese(p);
}
/* 访问登记设置
class AccessLevels{
public:
int getReadOnly(){ return readOnly; } //对只读属性进行只读访问
void setReadWrite(int val){ readWrite = val; } //对读写属性进行读写访问
int getReadWrite(){ return readWrite; }
void setWriteOnly(int val){ writeOnly = val; } //对只写属性进行只写访问
private:
int readOnly; //对外只读访问
int noAccess; //外部不可访问
int readWrite; //读写访问
int writeOnly; //只写访问
};
*/
/****************************************************************************************************
* 课堂练习01:设计一个Person类,具有name和age属性,提供初始化函数(Init),并提供对name和age的读写函数(set,get),
* 但必须确保age的赋值在有效范围内(0-100),超出有效范围,则拒绝赋值,并提供方法输出姓名和年龄。(10分钟)
****************************************************************************************************/
#include<string.h>
// 抽象类
class Person02{
private:
char mName[32]; // 定义类不要给成员初始化
char mGender[16];
int mAge;
public:
// 对成员变量mName进行写操作
void setName(char *name){strcpy(mName, name);}
// 对成员变量mName进行读操作
char* getName(void){return mName;}
// 对成员变量mGender进行写操作
void setGender(char *gender){
if (gender == "女" || gender == "男"){
strcpy(mGender, gender);
}else{
cout << "×××××性别输入错误,只能是“男”或“女”!×××××" << endl;
strcpy(mGender, "--");
}
}
// 对成员变量mGender进行读操作
char* getGender(void){return mGender;}
// 对成员变量mAge进行写操作
void setAge(int age){
if (age > 0 && age <= 125){
mAge = age;
}else{
cout << "×××××年龄输入错误,年龄区间为:(0, 125]!×××××" << endl;
mAge = -1;
}
}
// 对成员变量mAge进行读操作
int getAge(void){return mAge;}
// 初始化函数
void initPerson(char *name, char *gender, int age)
{
cout << "输入内容为:" << endl;
cout << "姓名:" << name << endl;
cout << "性别:" << gender << endl;
cout << "年龄:" << age << endl;
setName(name); // strcpy(mName, name);
setGender(gender); // strcpy(mGender, gender);
setAge(age); // mAge = age;
}
// 输出姓名、性别、年龄的方法
void showPerson(void)
{
cout << "最后输入内容为:" << endl;
cout << "姓名:" << getName() << endl;
cout << "性别:" << getGender() << endl;
cout << "年龄:" << getAge() << endl;
}
};
void test21()
{
char name[5] = "Lucy";
char gender[4] = "女";
Person02 per1; // 类的实例化:对象,其中Person02是类名称(抽象的概念),per1是对象实例(实际存在)
per1.initPerson(name, gender, -1);
per1.showPerson();
per1.setAge(16); // 重新设置年龄
per1.showPerson(); // 输出正确结果
}
int main()
{
// test12();
//test13();
//test14();
//test15();
//test16();
//test17();
//test18();
//test19();
//test20();
//test21();
return 0;
}
混合编程的两个文件
- C语言文件:MyModule01.c
// 混合编程:一般一个C文件就需要一个头文件用于被主函数文件进行引入,C文件中用于函数的定义,头文件中用于函数的声明
#include<stdio.h>
#include"MyModule01.h" // 引入C文件的头文件
void func1(){
printf("hello world!");
}
int func2(int a, int b){
return a + b;
}
- C语言文件对应的头文件:MyModule01.h
#ifndef MYMODULE01_H
#define MYMODULE01_H
#if __cplusplus // 宏:是否是C++
extern "C"{ // 外部C文件
#endif
void func1();
int func2(int a,int b);
#if __cplusplus
}
#endif
#endif // MYMODULE01_H