1、内联函数
常规与内联函数的区别:不在于编写的方式,而是C++如何将他们组合到程序中,编译生成可执行程序(机器指令),储存在内存(堆栈)中。当执行函数时,会保存当前指令的地址,执行完函数后,返回当地址。而内联函数与其他程序代码“内联”起来,也就是用相应的函数定义去替换函数调用,减少了跳转到函数调用的时间,以及函数调用完毕后返回的时间。速度会快,但是将会占用更多的内存空间。
使用的情况:代码量很少的时候。
(1)函数声明前需要加上inline
(2)函数定义前加上inline
一般直接将定义放在主函数前面(注:当程序过大或者函数中有递归时,编译器可能不会将它编译成内联函数)。
#include <iostream>
using namespace std;
inline double square(double x){return x * x;}
int main()
{
double a, b;
double c = 13.0;
a = square(5.0);
b = square(4.5 + 7.5);
cout <<"a = " << a << ", b = " << b << endl;
cout << "c = " << c << endl;
cout << "Now c = " << square(c++) << endl;
cout << "c = " << c << endl;
return 0;
}
2、引用变量
引用:定义变量的别名,又作函数参数(形参),将可操作原始数据,处理大型结构比较方便。
创建引用变量:
int rats;
int & rodents = rats;
引用更接近const指针,必须在创建时进行初始化,一旦关联某个变量,将一直效忠与它。
int * const pr = & rats == int & rodents = rats //他们作用相同
//在关联变量后,若在对rodents 进行下列语句
rodents = A; //这里不再是将将变量A与rodents绑定,而是将A的值赋值给rodents。
3、引用用作函数的参数
应用和指针一样,都是对原数据进行操作,在一些不希望改变原数据的情况,最好使用按值传递或则const修饰引用。
double cube(double a);
double recube(double &ra);
int main()
{
double x = 3.0;
cout << cube(x) << "cube of " << x << endl;
cout << recube(x) << "cube of " << x << endl;//不适合使用引用
//可将函数声明为
//double recube(const double &ra)
return 0;
}
double cube(double a)
{
return a * a * a;
}
double recube(double &ra)
{
ra *= ra * ra;
return ra;
}
注意:
(1)对于常引用,当时参与形参不匹配时,将生成临时变量与引用相关联(其行为类似于按值传递)。
(2)对于非常引用,不能将常量与引用关联,而常引用可以。
使用const的好处: 可以避免无意的编程错误。
可以接受const 和 非const。
使用const引用使函数能够正确生成临时变量。
4、将引用用于结构体
引用非常适合结构体和类
对于一个函数,若返回值为结构体,则返回值为结构体的拷贝,此时,将花费时间去拷贝复制,造成效率降低以及浪费内存空间。若返回值为结构体的引用或指针,则返回值为结构体本身。
#include <iostream>
#include <string>
using namespace std;
struct free_throws
{
string name;
int made; // 成功的次数
int attempts; //尝试的次数
float percent;
};
void display(const free_throws &ft);
void set_pc(free_throws &ft);
free_throws & accumulate(free_throws &target, const free_throws &source);
int main()
{
free_throws one = {"Rick", 13, 14};
free_throws two = {"Jack", 10, 16};
free_throws three = {"Jerry", 7, 9};
free_throws four = {"Jason", 5, 9};
free_throws five = {"Micheal", 6, 14};
free_throws team = {"Class 1", 0, 0};
set_pc(one);
display(one);
display(accumulate(team, one));
display(accumulate(accumulate(accumulate(team,two),four),five));
return 0;
}
void set_pc(free_throws &ft)
{
if(ft.attempts != 0)
{
ft.percent = 100* float(ft.made) / float(ft.attempts);
}
else
ft.percent = 0;
}
void display(const free_throws &ft)
{
cout << "Name: " << ft.name << endl;
cout << "Made: " << ft.made << '\t';
cout << "Attempts: " << ft.attempts << '\t';
cout << "percent: " << ft.percent << endl;
}
free_throws & accumulate(free_throws &target, const free_throws &source)
{
target.attempts += source.attempts;
target.made += source.made;
set_pc(target);
return target;
}
注意:避免返回引用临时变量的引用(指针),例如下列程序,返回的是临时结构体temp,这是错误的,当程序执行完毕后,临时变量将会被销毁,无法在进行引用。
//例如下列程序
free_trows & example(const free_trows & source)
{
free_trows temp;
temp.attempts += source.attempts;
temp.made += source.attempts;
return temp;
}
5、引用和类
//可以返回临时变量
string version(const string &s1, const string &s2);
//由于返回的是string类的引用,故不能返回临时变量
const string & version2(string &s1, const string &s2);
6、对象,继承和引用
基类引用可以指向派生类(即可指向基类本身也可指向派生类)。
派生类继承基类的方法,可以使用基类的特性。
7、何时使用引用参数
引用的原因:能够修改调用函数中的对象
提高运行的速度
指导原则:数据对象很小时,按值传递
若对象是数组,只能使用指针
数据对象较大,使用指针或引用
8、默认参数
对于某些函数,通常使用的是默认的参数(默认值),但偶尔也会传递其他值,这时即可使用默认值,对于带参数列表的函数,必须从右到左添加默认值。
实参从左到右添加参数,不能跳过任何参数。默认值在声明处给出,不是在函数定义处。
#include <iostream>
using namespace std;
const int ArSize = 80;
char * left(const char * str, int n = 1);
int main()
{
char sample[ArSize];
cout << "Enter a string: " << endl;
cin.get(sample, ArSize);
char *ps = left(sample, 4);
cout << ps << endl;
delete []ps;
ps = left(sample);
cout << ps << endl;
delete []ps;
return 0;
}
char * left(const char * str, int n)
{
if(n < 0)
n = 0;
char * p = new char[n + 1];
int i;
for(i = 0; i < n && str[i]; i++)
{
p[i] = str[i];
}
while (i <= n)
{
p[i++] = '\0';
}
return p;
}
9、函数重载(函数多态)
主要体现在函数的形式相同,函数列表不同。
C++ 的左值与右值 左值与右值引用。
左值与右值相对于 = += 运算符。左值:能取地址,右值:不能取地址。
const int &d = 10; //将创建临时变量值为10,并与d绑定
int && x = 10; //右值引用
重载的关键:参数的个数、参数的类型、参数的排列顺序(形参)。
何时使用:执行的任务相同,但使用不同的数据类型。
//函数名相同,但参数不同,类型、排列顺序。返回值不影响函数的重载。
char * left(const char * str, int n = 1);
unsigned long left(unsigned long num, unsigned int ct);
注意:有多个函数重载时,若在调用的时候没有与其中任何一个匹配,可能会终止运行,且不会进行强制转换。
下面情况将产生二义性:编译器将无法判断调用哪一个函数。
double cube(double x);
double cube(double &x);
long gronk(int m, double n);
double gronk(int m, double n);
10、函数模板
基本语法:
template <typename T>
void swap(T a, T b);
在调用时,编译器将根据调用生成不同的函数,一般将函数模板放在头文件里面。
模板的重载:同函数重载一样,函数名相同, 形参的个数,类型,顺序不同即可构成重载。
template <typename T>
void Swap(T ar1[], T ar2[], int n);
template <typename T>
void Swap(T &a, T &b);
函数模板很强大,但是同样并不适用所有的数据类型,因为传入的类型不一定是基本的数据类型,传入的形参不变,函数重载也没有办法解决,于是出现了函数模板的显示具体化。
template < > void Swap<job>(job &j1, job &j2);//job为结构体类型
模板的实例化与具体化
编译器使用函数模板为特定类型生成函数定义时,得到函数模板实例,这个过程是在编译时完成的。
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
int main() {
int a = 3, b = 5;
int c = max(a, b); // 实例化生成一个 int 类型的 max 函数
return 0;
}
具体化,指的是显示实例化模板,即在程序中为模板提供实参,强制编译器生成特定类型的函数或类。
template <typename T>
T max(T a, T b) {
return a > b ? a : b;
}
template int max<int>(int a, int b); // 显式实例化生成一个 int 类型的 max 函数
int main() {
int a = 3, b = 5;
int c = max(a, b);
return 0;
}
11、重载解析(编译器该选择哪一个函数版本)
原则:谁最批配,最省事,最佳。