C++ Primer Plus(嵌入式公开课)---第8章 函数探幽

0308

C++ Primer Plus - 第八章

  • 第七章 函数---C++的编程模块
  • 第八章 函数探幽
    • 8.1 C++内联函数
    • 8.2 引用变量---references
      • 8.2.1 创建引用变量
      • 8.2.2 将引用用作函数参数
      • 8.2.3 const引用 和 临时变量
      • 8.2.4 将引用用于结构体---返回结构体引用
        • 为什么要返回结构体引用?
        • 返回引用时需要注意的问题
      • 8.2.5 将引用用于类对象(string类对象)
      • 8.2.6 对象、继承和引用
      • 8.2.7 总结:何时使用引用参数?
    • 8.3 默认参数(为函数参数设定默认值)
    • 8.4 函数重载(函数多态)
      • 8.4.1 函数重载示例
      • 8.4.2 何时使用函数重载
    • 8.5 函数模板(Template)
      • 8.5.0 函数模板---泛型编程思想
      • 8.5.1 函数模板的重载
      • 8.5.2 模板的局限性
      • 8.5.3 显式具体化
        • 非模板函数 & 模板函数(常规模板函数) & **显式具体化**模板函数:
        • 各种类型的显式具体化模板:
        • 一个`常规模板`和`具体化模板`的例子:(具体化模板 优于 常规模板)
      • 8.5.4 实例化和具体化
        • 模板 & 函数定义 & 模板实例 & 实例化:
        • 隐式实例化 & 显式实例化
        • 模板即函数声明;模板实例即函数定义;
        • 显式实例化 & 显式具体化
        • 总结:
        • 一个简单的例子:`非模板函数` 优于 `具体化模板` 优于 `常规模板`
      • 8.5.5 编译器选择使用哪个函数版本
        • 提升转换 & 标准转换
        • 结论
      • 8.5.6 模板函数的发展---关键字`decltype`
    • 8.6 总结
    • 8.7 复习题
      • 1. 哪种函数适合定义为内联函数?
      • 2. 给函数原型设置默认参数,函数定义需要做什么修改?
      • 6. 默认参数 和 函数重载(重点看题目b)
        • 补充:题目b的尝试
      • 8.实现一个模板的具体化
        • 注意:要实现模板的具体化,一个首要前提是有一个模板。
      • 9. decltype关键字
    • 8.8 编程练习
      • 第1题:默认参数 和 static变量
        • static变量记录函数被调用的次数
      • 第2题:char* 和 const char* 和 字符数组 和 string constant 和 字符串常量
        • 先看题目2:
        • 使用默认参数时在函数原型中设置默认值,函数定义的参数不要再写默认值了,否则会报错!!!
        • char* 和 const char* 和 字符数组 和 `strcpy()`函数
        • const string 与const string&
        • string constant 和 字符串常量
      • 第4题:字符数组 和 char*
        • 注意1和2
      • 第6题:常规函数模板 和 具体化模板
        • string 和 const char* 和字符串常量
        • char* 和 字符数组 和 strcpy()函数
  • 第9章 内存模型和名称空间

第七章 函数—C++的编程模块

点这里

第八章 函数探幽

8.1 C++内联函数

要使用C++内联函数这项特性,通常的做法是省略原型,用函数定义替代函数原型,并在函数定义前加上关键字inline。只有在函数定义的内容很短时,才能用内联方式。

示例:

#include

using std::cout; using std::cin; using std::endl;

inline double square(double x) { return x * x;}
int main(){

    double a,b;
    cout << "a = " << square(5.0) << endl;
    cout << "b = " << square(4.5 + 7.5) << endl;

    return 0;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第1张图片

8.2 引用变量—references

C++新增一种复合类型—引用变量。引用是以定义的变量的别名(另一个名称)。
引用变量的主要用途是用作函数的形参:通过将引用变量用作参数,函数将使用原始数据,而不是其副本。

C和C++使用&符号来指示变量的地址;
C++给&符号赋予了另一个含义:将其用作声明引用,例如,int& 指的是 指向int的引用

8.2.1 创建引用变量

看个示例:

#include

using std::cout; using std::cin; using std::endl;

int main(){
    int rats = 10;
    int& rodents = rats;
    cout << "rats = " << rats << "; &rats = " << &rats << endl;//10 addr1
    cout << "rodents = " << rodents << "; &rodents = " << &rodents << endl;//10 addr1
    rodents++;
    cout << "rats = " << rats << "; &rats = " << &rats << endl;//11 addr1
    cout << "rodents = " << rodents << "; &rodents = " << &rodents << endl;//11 addr1

    return 0;
}

分析:

  1. 变量rodents 的类型时int&,即指向int变量rats的引用;
  2. &rats&rodents中的&表示地址运算符;
  3. 必须在声明引用变量时进行初始化,有点类似于指针常量,即指向固定;(7.3.5 指针和const (常量指针&指针常量))
  4. 引用的典型用途是作为函数参数,具体地说是结构体和(类)对象参数,即引用多用于指向结构体和类对象。

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第2张图片
如果增加以下代码:

	int bunnies = 50;
    rodents = bunnies;
    cout << "rats = " << rats << "; &rats = " << &rats << endl;//50 addr1
    cout << "rodents = " << rodents << "; &rodents = " << &rodents << endl;//50 addr1
    cout << "bunnies = " << bunnies << "; &bunnies = " << &bunnies << endl;//50 addr2

结果是:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第3张图片
所以说rodentsrats是等价的,rodents = bunnies;就相当于rats = bunnies;
rodentsrats的地址一直相同,内容也一直相同。

8.2.2 将引用用作函数参数

引用常被用作函数参数,使得函数中的变量名成为调用程序中的变量的别名,这种传递参数的方法称为按引用传递

示例:

#include

using std::cout; using std::cin; using std::endl;

//程序清单8.4:
void swapr(int& a,int& b);
void swapp(int* a,int* b);
void swap(int a,int b);

int main(){

//程序清单8.4:
    int m = 20;
    int n = 30;
    cout << "初始值:m = " << m << "; n = " << n << endl;//20 30

    int& a = m;
    int& b = n;
    cout << "使用引用进行交换:";
    swapr(a,b);
    cout << "m = " << m << "; n = " << n << endl;//30 20

    int* pm = &m;
    int* pn = &n;
    cout << "使用指针进行交换:";
    swapp(pm,pn);
    cout << "m = " << m << "; n = " << n << endl;//20 30

    cout << "使用值传递进行交换:";
    swap(m,n);
    cout << "m = " << m << "; n = " << n << endl;//20 30

    return 0;
}

void swapr(int& a,int& b){
    int temp;
    temp = a;
    a = b;
    b = temp;
}
void swapp(int* a,int* b){
    int temp;
    temp = (*a);
    (*a) = (*b);
    (*b) = temp;
}
void swap(int a,int b){
    int temp;
    temp = a;
    a = b;
    b = temp;
}

分析:
按引用传递的结果和按地址传递的结果相同,都能完成数据的交换,只有按值传递不能完成数据交换。

8.2.3 const引用 和 临时变量

先看示例:

#include

using std::cout; using std::cin; using std::endl;

//程序清单8.5:
int cube(int a);
int cube1(int a);
int recube(int& ra);
int recube1(int& ra);

int main(){
//程序清单8.55:
    int m = 3;
    int n = 5;
    cout << "m = " << m << "; n = " << n << endl;
    cout << "cube(m) = " << cube(m) << "; m = " << m << endl;
    cout << "recube(n) = " << recube(n) << "; n = " << n << endl;
    m = 3; n = 5;
    cout << "cube1(m) = " << cube1(m) << "; m = " << m << endl;
    cout << "recube1(n) = " << recube1(n) << "; n = " << n << endl;

    return 0;
}

int cube(int a){
    return a * a * a;
}
int cube1(int a){
    a = a * a * a;
    return a;
}
int recube(int& ra){
    return ra * ra * ra;
}
int recube1(int& ra){
    ra = ra * ra * ra;
    return ra;
}

分析:
cube()和cube1() 的区别在于子函数中有没有对实参的值进行修改,但由于是值传递,所以形参a是实参m的拷贝,所以即使在cube1()中a的值有变化,也不会影响m的值;
recube()和recube1() 的区别也在于子函数中有没有对实参的值进行修改,但由于是引用传递,所以在recube()和recube1()中,形参ra和实参n是一个东西,所以在recube1()中,当ra的值发生变化,main函数中的n也发生了变化。

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第4张图片
如果不想影响到实参n的变化,可以考虑把引用参数前面加个const,这样做之后,当编译器发现子函数中修改了ra的值,就会报错,如下:

int recube1(const int& ra){
    ra = ra * ra * ra;//这里会报错:ra必须是可修改的左值
    return ra;
}

临时变量:
如果引用参数是const实参与引用参数不匹配,编译器会在下面两种情况下生成临时变量:

  • 实参的类型正确,但不是左值;
  • 实参的类型不正确,但可以转换为正确的类型。

左值:可修改的值,例如普通变量,上面例子中的mn
非左值:不可修改的值,例如常量(10)和表达式(m+3);

修改上面的recube()

//const引用参数
int recube(const int& ra){//引用形参是const
    return ra * ra * ra;
}

然后看下面的程序:

int side = 26;
int* ps = &side;
int& rs = side;
short age = 5L;
int lens[4] = {1,2,3,4};

recube(side);//形参ra等于side
recube(lens[2]);//形参ra等于lens[2]
recube(rs);//形参ra等于rs,等于side
recube(*ps);//形参ra等于(*ps),等于side
recube(age);//实参short型变量age类型不匹配,但可以转换为int类型,编译器会生成一个临时匿名变量,并让ra指向它,所以ra是临时变量的引用
recube(500);//实参500的类型正确,但不是左值,是个常量,编译器会生成一个临时匿名变量,并让ra指向它,所以ra是临时变量的引用
recube(side + 10);//实参side + 10的类型正确,但不是左值,是个表达式,编译器会生成一个临时匿名变量,并让ra指向它,所以ra是临时变量的引用

上面的倒数三行代码,只有当形参是const时才可行;
没const的话那三行会编译错误:

  • recube(500);和recube(side + 10);提示非常量引用的初始值必须为左值
  • recube(age);提示无法用short类型的值初始化int&类型的引用(非常量限定)

实际上,对于形参为const引用的C++函数,如果实参不匹配(上面的两种情况),则其行为类似于按值传递,为确保原始数据不被修改,就生成临时变量来临时存储实参值。

结论:应尽可能将引用形参声明为const。理由有三:

  • 使用const可以避免无意中修改数据的编程错误
  • 使用const使函数能够处理const和非const实参,否则只能接受非const数据;、
  • 使用const引用是函数能够正确生成并使用临时变量

8.2.4 将引用用于结构体—返回结构体引用

引用非常适合用于结构体和类(用户自定义类型)

示例:

#include
#include

using std::cout; using std::cin; using std::endl; using std::string;

//程序清单8.6:
struct Students{
    string name;
    int num;
};
void showStu(const Students& stu);
Students& accumulate(Students& sum_stu,const Students& one_stu);//返回结构体引用

int main(){

//程序清单8.6:
    Students stu0 = {"sum"};
    Students stu1 = {"reus",26};
    Students stu2 = {"messi",50};
    Students stu3 = {"ronaldo",60};
    Students stu4 = {"marcelo",20};
    Students stu5 = {"benzema",10};
    Students stu6;
//①
    showStu(stu0);//0
//②
    accumulate(stu0,stu1);//stu0 + stu1
    showStu(stu0);//26
//③
    showStu(accumulate(stu0,stu2));//76 stu0 + stu2
//④
    accumulate(accumulate(stu0,stu3),stu4);//(stu0 + stu3) + stu4
    showStu(stu0);//156
//⑤
    accumulate(stu0,stu5) = stu4;//stu4给到stu0
    showStu(stu0);//marcelo 20
//⑥
    stu6 = accumulate(stu0,stu5);//stu0 + stu5
    showStu(stu6);//30

    return 0;
}

void showStu(const Students& stu){
    cout << "姓名:" << stu.name << ";数量:" << stu.num << endl;
}
Students& accumulate(Students& sum_stu,const Students& one_stu){
    sum_stu.num += one_stu.num;
    return sum_stu;
}

分析:
1.accumulate()的返回值是结构体引用,不是结构体;
2.showStu(accumulate(stu0,stu2));//76 stu0 + stu2 是把返回值直接打印出来,返回的是stu0的引用,也就是stu0本身;
3.accumulate(accumulate(stu0,stu3),stu4);//(stu0 + stu3) + stu4 是先将(stu0stu3)相加,然后把返回值stu0的引用(也就是stu0本身)作为外面这层accumulate()的第一个参数;
4.accumulate(stu0,stu5) = stu4;//stu4给到stu0 虽然是先把(stu0stu5)相加的结果给到了stu0,但是返回值stu0又被赋上stu4的值,所以accumulate()实际上没起到作用,就相当于是直接stu0 = stu4;
5.stu6 = accumulate(stu0,stu5);//stu0 + stu5 先把(stu0stu5)相加的结果给到了stu0,然后把stu0赋值给stu6

为什么要返回结构体引用?

☆☆☆ 6.如果子函数原型是Students accumulate(Students& sum_stu,const Students& one_stu);//返回值是结构体 那么对于stu6 = accumulate(stu0,stu5);//stu0 + stu5 来说,就是先把(stu0stu5)相加的结果给到了stu0,然后把stu0复制到一个临时位置,再将这个拷贝复制给stu6,这样的效率就会低,这也就是为什么要返回结构体引用的原因。

返回引用时需要注意的问题

☆☆☆☆☆最后是返回引用时需要注意的问题:要避免返回(函数终止时不再存在的内存单元的)引用,即(在子函数中声明的局部变量的)引用,类似这样的代码:

const Students& clone1(Students stu){
	Students temp
	temp = stu;
	return temp;
}

这个函数返回的是指向临时变量temp的引用,函数执行完temp将不再存在!即返回的内存空间根本就不存在。
为避免这种问题,最简单的方法是,
返回一个(作为参数)传递给函数的引用
,即返回形参列表中的一个引用,例如上面的示例程序中返回形参stu0的引用

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第5张图片

8.2.5 将引用用于类对象(string类对象)

和8.2.4类似,也是强调了返回引用时需要注意的问题:要避免返回(函数终止时不再存在的内存单元的)引用,即(在子函数中声明的局部变量的)引用

8.2.6 对象、继承和引用

能够将一个特性从一个类传递给另一个类的语言特性被称为继承
例如,ostream类是基类,而ofstream类是派生类,派生类继承了基类的方法,就意味着ofstream对象可以使用基类的特性。

同理,基类引用可以指向派生类对象,而无需进行强制类型转换。因此,可以定义一个(将基类引用作为参数的)函数,调用该函数时,可以将基类对象作为参数,也可将派生类对象作为参数。
例如,参数类型为ostream&的函数可以接受ostream类对象(例如cout)作为参数,也可以接受(用户自己生命的ofstream类对象ofs)作为参数。

因此就可以写这样一个函数:
给形参传递cout时就是将内容显示到屏幕上;
给形参传递ofs时就是将内容写到文件中。

看示例:

#include
#include
#include
#include

using std::cout; using std::cin; using std::endl; using std::string; using std::ostream; 
using std::ofstream; using std::ios_base;using std::ios;


//程序清单8.8:
void printOrWrite(ostream& os, const string& name,const int arr[]);


int main(){
//程序清单8.8:
    string name = "my name is Reus.";
    int arr[5] = {1,2,3,4,5};
    // cout << name << endl;
    // int i;
    // for(i = 0;i < 4;i++)
    //     cout << arr[i] << ", ";
    // cout << arr[i] << endl;

    //写到文件中:
    string fileName = "test.txt";
    ofstream ofs;
    ofs.open(fileName);
    if(!ofs.is_open()){
        cout << "文件打开错误!" << endl;
        return -1;
    }
    printOrWrite(ofs,name,arr);

    //打印:
    printOrWrite(cout,name,arr);
    
    return 0;
}

void printOrWrite(ostream& os, const string& name,const int arr[]){

    os << name << endl;
    int i;
    for(i = 0;i < 4;i++)
        os << arr[i] << ", ";
    os << arr[i] << endl;

}

分析:
1.刚开始一直编译不成功,找了很久,后来发现是printOrWrite()函数原型中string前面少写了个const
2.单独写一个函数,这样打印写入到文件就可以直接调用,会很方便,这就是一个派生类继承基类方法的案例;
3.引用值传递效率要更高,但要警惕是否会改动到原始数据。

8.2.7 总结:何时使用引用参数?

使用引用参数的主要原因有两个:

  • 程序员能够修改调用函数中的数据对象;
  • 通过传递引用而不是整个数据对象,可以提高程序的运行速度

那么,什么时候应使用引用、什么时候应使用指针呢?什么时候应按值传递呢?
下面是一些指导原则:

数据对象 使用传递的值但不做修改的函数 要修改调用函数中数据的函数
内置数据类型或小型结构体 按值传递 使用指针
数组 const指针(唯一的选择) 使用指针(唯一的选择)
大型结构体 const指针const引用 引用或指针
类对象 const引用 引用
备注:传递类对象参数的标准方式是按引用传递。

当然,这只是一些指导原则,很可能有充分的理由做出其他的选择。
例如,对于基本类型,cin 使用引用,因此可以使用 cin >> n,而不是cin >> &n(P220 8.2.5上面的一行)。

8.3 默认参数(为函数参数设定默认值)

1.设置默认值,必须通过函数原型

void func1(string name,int m = 1);//函数原型

在main函数中调用func1时,
如果没传入第二个参数,那就相当于是使用了默认值,例如:func1("reus");就相当于func1("reus",1);
如果传入了第二个参数,那么默认值将被覆盖,例如:func1("reus",3);中3将覆盖默认值

2.要为某个参数设置默认值,必须为其右边所有的参数提供默认值;

int func2(int m,int n = 4,int l = 5);//ok
int func3(int m,int n = 4,int l);//不ok
int func4(int m = 3,int n = 4,int l = 5);//ok 

3.只需在函数原型中指定默认值,函数定义中不需要再次指定默认值;
看个示例:

#include
#include

using std::cout; using std::cin; using std::endl; using std::string;

//程序清单8.9:
void func1(string name,int m = 1);//函数原型:设置了默认值

int main(){
//程序清单8.9:
    func1("reus");
    func1("messi",3);
    
    return 0;
}
void func1(string name,int m){//函数定义中不需要再次指定默认值
    cout << "name = " << name << "; m = " << m << endl;
}

结果:
在这里插入图片描述

8.4 函数重载(函数多态)

概念:
函数重载函数多态,能够让用户使用多个同名的函数,这两个术语指的是一回事,但我们通常使用函数重载。

要点1234:
1.函数重载的关键在于函数的参数列表,也称为函数特征标(function signature)。首先是两个函数的函数名相同,其次是两个函数的参数个数、参数类型、参数的排列顺序不同,则可以构成重载,例如:

void func(int n,float m);
void func(int n,float m,float x);//参数个数不同
void func(int n,int m);//参数类型不同
void func(float m,int n);//参数的顺序不同

2.变量名和返回值类型就无关紧要了,不是构成重载的必要条件,例如:

long func1(int n,float m);
//double func1(int n,float m);//不ok
//long func1(int x,float y);//不ok

参数的个数、类型、顺序都相同,仅仅是返回值类型和变量名不一样,所以不能构成重载。无论返回值类型和变量名是否相同,都与构成重载无关

3.另外,某种数据类型引用和这种数据类型本身是同一个东西,所以不能构成重载,例如:

double cube(double x);
//double cube(double& x);//不能构成重载

引用和自身是一回事,因此二者不能共存。

4.在匹配参数时,要注意区分const变量非const变量,例如:

void func2(char* name);
void func2(const char* name);
void func3(char* name);

参数类型相同,所以构成重载,但在调用func2的时候要注意匹配参数,

char* name1 = "123asdfg";
const char* name2 = "456asdfqwe";
func2(name1);//func2(char* name);
func2(name2);//func2(const char* name);
func3(name1);//func3(char* name);
//func3(name2);//错误,没有与之匹配的函数

函数func3不能接受name2,因为func3只能接受非const的变量,而name2const变量。所以说:

  • 非const参数 只与 非const变量 匹配;
  • const参数非const变量、const变量、右值参数(表达式) 都匹配。

8.4.1 函数重载示例

代码:

#include
#include
#include

using std::cout; using std::cin; using std::endl; using std::string;

//程序清单8.10:
unsigned long func1(const long& num,int n);//返回数字num的前n位数字
char* func1(const char* str,int n);//返回字符串str的前n个字符

int main(){

//程序清单8.10:
    cout << "程序清单8.10的结果:" << endl;
    unsigned long num = 12345;
    const char* str = "asdfg";
    char* res;
    for(int i = 0;i < 7;i++){
        cout << func1(num,i) << endl;
        res = func1(str,i);
        cout << res << endl;
        delete[] res;
    }
    cout << "Done!" << endl;
    return 0;
}
unsigned long func1(const long& num,int n){
    int weishu = 0;
    unsigned long subNum = 0;
    unsigned long temp = num;
    while(temp > 0){
        weishu++;
        temp /= 10;
    }
    //cout << num << "的位数是:" << weishu << endl;
    if(num == 0 || n == 0){
        return 0;
    }
    if(weishu < n)
        return num;//返回原数字
    
    subNum = num;
    for(int i = 0;i < (weishu - n);i++){//要显示前n位,那就进行(weishu - n)次/10操作
        subNum = subNum / 10;
    }
    //cout << num << "的前" << n << "位数是:" ;
    return subNum;
}
char* func1(const char* str,int n){
    
    char* res = new char[n + 1];
    int i;
    for(i = 0;i < n && str[i] != '\0';i++){
        res[i] = str[i];
    }
    while(i <= n)
        res[i++] = '\0';
    return res;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第6张图片

8.4.2 何时使用函数重载

仅当函数基本上执行相同的任务,但使用不同形式的数据(不同的数据类型、个数、顺序)时,才应采用函数重载。

8.5 函数模板(Template)

8.5.0 函数模板—泛型编程思想

函数模板是通用的函数描述,也就是说他们使用 泛型 来定义函数,其中的泛型可用具体的类型(intdouble)替换。通过将类型作为参数传递给模板,可是编译器生成该类型的函数。

例如,想要交换各种数据类型(int型、float型、double型)的两个数据,一种方法是利用上一节的函数重载,如下:

void swap(int& a,int& b);
void swap(float& a,float& b);
void swap(double& a,double& b);

但是这样就需要写多个函数原型和函数定义,既浪费时间,又容易出错。

因此,可以考虑建立一个交换两个数的模板

template <typename T>
//template 
void swap(T& a, T& b);//函数原型

其中templatetypename 是必需的,也可以用class替换typename
函数原型上面的代码表示:建立一个模板,并将类型命名为T。这个T可以用任何具体的类型(如int型、float型、double型)替换。
注意:模板并不创建任何函数,而是告诉编译器如何定义函数

模板函数的定义

//template 
template <class T>
void swap(T& a, T& b){
	T temp;
	temp = a;
	a = b;
	b = temp;
}

示例:

#include
#include
#include

using std::cout; using std::cin; using std::endl; using std::string;

//建立一个模板函数:
template <typename T>
//template 
void swap(T& a, T& b);

int main(){
    int a,b;
    a = 30; b = 50;
    cout << "交换前,a = " << a << ";b = " << b << endl;
    swap(a,b);
    cout << "交换后,a = " << a << ";b = " << b << endl;

    double x,y;
    x = 30.6; y = 50.9;
    cout << "交换前,x = " << x << ";y = " << y << endl;
    swap(x,y);
    cout << "交换后,x = " << x << ";y = " << y << endl;

    return 0;
}
//模板函数的定义:
//template 
template <class T>
void swap(T& a, T& b){
	T temp;
	temp = a;
	a = b;
	b = temp;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第7张图片

分析:

  1. 何时使用函数模板?
    如果需要多个将同一种算法用于不同类型的函数,请使用模板。
  2. 函数模板不能缩短可执行程序。上述示例中,最终的代码中不包含任何模板,而是包含了为程序生成的实际函数void swap(int& a, int& b)void swap(double& a, double& b)。使用模板的好处是,它使得生成多个函数定义的行为更简单、更可靠
  3. 一般来说,都会将模板放到头文件中,并在需要使用模板的文件中包含这个头文件

8.5.1 函数模板的重载

函数模板也有重载的情况(函数名相同,但参数个数和参数类型不同),例如:

//交换两个数的模板:
template <typename T>
void swap(T& a, T& b);

//交换两个数组的模板:
template <typename T>
void swap(T a[], T b[],int n);
//void swap(T* a, T* b,int n);

示例:

#include

using std::cout; using std::cin; using std::endl; using std::string;

//交换两个数的模板:
template <typename T>
//template 
void swap(T& a, T& b);//原型

//交换两个数组的模板:
template <typename T>
void swap(T a[], T b[],int n);//原型
//void swap(T* a, T* b,int n);

template <typename T>
void showArr(T arr[],int n);

int main(){
//程序清单8.12:
    int arr1[5] = {1,2,3,4,5};
    int arr2[5] = {6,7,8,9,0};
    cout << "修改前:" << endl;
    showArr(arr1,5);
    showArr(arr2,5);
    swap(arr1,arr2,5);
    cout << "修改后:" << endl;
    showArr(arr1,5);
    showArr(arr2,5);

    double arr3[5] = {1.1,2.2,3.3,4.4,5.5};
    double arr4[5] = {6.6,7.7,8.8,9.9,0.0};
    cout << "修改前:" << endl;
    showArr(arr3,5);
    showArr(arr4,5);
    swap(arr3,arr4,5);
    cout << "修改后:" << endl;
    showArr(arr3,5);
    showArr(arr4,5);

    return 0;
}
//模板函数的定义:
//template 
template <class T>
void swap(T& a, T& b){
	T temp;
	temp = a;
	a = b;
	b = temp;
}

template <typename T>
void swap(T a[], T b[],int n){
    T temp[n];
    for(int i = 0;i < n;i++){
        temp[i] = a[i];
        a[i] = b[i];
        b[i] = temp[i];
    }
}

template <typename T>
void showArr(T arr[],int n){
    int i;
    for(i = 0;i < n-1;i++){
        cout << arr[i] << ",";
    }
    cout << arr[i] << "." << endl;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第8张图片

8.5.2 模板的局限性

template <class T>
void swap(T& a, T& b);

template <class T>
void swap(T& a, T& b){
	b = a;
	if(a > b){}
	T c = a * b;
}

分析:
1.如果T是数组,那么b = a;就不成立,因为这样只会把数组a的地址给到b,不能实现整体的赋值;
2.如果T是结构体,那么 if(a > b){}就不成立,因为结构体没办法比较大小;
3.如果T数组、指针或者结构体,那么T c = a * b;就不成立。
一种方法是考虑重载运算符;还有一种方法是为特定类型提供具体化的模板定义,见下一节。

8.5.3 显式具体化

非模板函数 & 模板函数(常规模板函数) & 显式具体化模板函数:
//结构体:
struct jober
{
    string name;
    int age;
    double salary;
};
//非模板函数:
void swap(job &, job &);
//模板函数:(常规模板函数)
template <typename T>
void swap(T &, T &);
//显式具体化模板函数:
template<> void swap<job>(job &, job &);

结论:
优先级:非模板函数 优于 显式具体化模板 优于 常规模板

各种类型的显式具体化模板:

先要有一个常规模板,才能创建各个具体类型的具体化模板

//常规模板:
template<class T>
void Swap(T &, T &);


//int类型的具体化模板:
template <> void Swap<int>(int& ,int&);//ok
template <> void Swap(int& ,int&);//也ok,可以省略第二个<>

//结构体类型的具体化模板:
struct jober
{
    string name;
    int age;
    double salary;
};
template<> void swap<jober>(jober& a,jober& b);//ok
template<> void swap(jober& a,jober& b);//也ok,可以省略第二个<>
一个常规模板具体化模板的例子:(具体化模板 优于 常规模板)
#include
#include
#include

using std::cout; using std::cin; using std::endl; using std::string;

//程序清单8.13:
//常规模板函数:
template <typename T>
void swap(T& a,T& b);//常规模板的原型声明

struct jober
{
    string name;
    int age;
    double salary;
};
//具体化模板函数:
template<> void swap<jober>(jober& a,jober& b);//具体化模板的声明

void showStruct(const jober& j);//非模板函数的声明

int main(){

    int a,b;
    a = 20; b = 30;
    cout << "交换前:a = " << a << ";b = " << b << endl;
    swap(a,b);
    cout << "交换后:a = " << a << ";b = " << b << endl;

    jober j1 = {"reus",26,50.5};
    jober j2 = {"messi",28,30.6};
    cout << "交换前:" << endl;
    showStruct(j1);
    showStruct(j2);
    swap(j1,j2);
    cout << "交换后:" << endl;
    showStruct(j1);
    showStruct(j2);

    return 0;
}
template <typename T>
void swap(T& a,T& b){//模板函数的实现
    T temp;
    temp = a;
    a = b;
    b = temp;
}
template<> void swap<jober>(jober& a,jober& b){//模板函数具体化的实现
    string tempName;
    int tempAge;
    double tempSalary;
    tempName = a.name;
    a.name = b.name;
    b.name = tempName;
    tempAge = a.age;
    a.age = b.age;
    b.age = tempAge;
    tempSalary = a.salary;
    a.salary = b.salary;
    b.salary = tempSalary;
}
void showStruct(const jober& j){//非模板函数的实现
    cout << "姓名:" << j.name << "; 年龄:" << j.age << ";薪资:" << j.salary << endl;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第9张图片

8.5.4 实例化和具体化

模板 & 函数定义 & 模板实例 & 实例化:

请记住,模板并不等同函数定义,在包含函数模板的代码中也并不会自动生成函数定义,模板只是提供了一个生成函数定义的方案。编译器使用模板为特定类型生成函数定义,得到一个此类型的模板实例(instantiation),这就是一个实例化的过程。

例如在上一节程序清单8.13中,main函数调用swap(a,b);之后编译器会生成swap()模板的一个实例,该实例使用的是int类型,因此说该实例是一个int类型的函数定义,这就是一个实例化的过程,并且这种实例化方式被称为隐式实例化(implicit instantiation)。
此外,C++还允许显式实例化(explicit instantiation),这种实例化方式可以直接命令编译器创建特定的实例,如下:

template void Swap<int>(int&, int&);

编译器看到上述声明后,就会使用swap()模板生成一个int类型的模板实例,即上述声明的意思是“使用swap()模板生成int类型的函数定义”。

综上,
1.模板实例类似于函数定义,模板类似于函数声明;
2.利用模板生成模板实例的过程即实例化。

隐式实例化 & 显式实例化

二者的区别在于:
隐式实例化是在函数调用swap(a,b)时,编译器使用swap()模板生成一个int类型的模板实例
而显式实例化是直接通过声明的方式来直接命令编译器使用swap()模板生成一个int类型的模板实例

模板即函数声明;模板实例即函数定义;

使用模板生成一个具体类型的模板实例,也即使用模板生成具体类型的函数定义:
使用模板生成一个int类型的模板实例,也即使用模板生成int类型的函数定义;
使用模板生成一个double类型的模板实例,也即使用模板生成double类型的函数定义;
使用模板生成一个结构体类型的模板实例,也即使用模板生成结构体类型的函数定义;

显式实例化 & 显式具体化
//显式实例化:
template void Swap<int>(int&, int&);
//显式具体化:
//int类型的具体化模板:
template <> void Swap<int>(int& ,int&);//ok
template <> void Swap(int& ,int&);//也ok,可以省略第二个<>

显式具体化的意思是“不要使用swap()模板来生成函数定义(模板实例)而应使用(专门为int类型显式定义的)函数定义”,即这个原型有且必须要有自己的函数定义

注意:
1.在一个文件中同时使用同一种类型显式示例显式具体化将会报错!!!
2.二者的区别在于显式具体化的声明使用前缀template <>,而显式实例化的声明使用template,没有<>

总结:

1.隐式实例化、显式实例化和显式具体化统称为具体化specialization。他们的相同之处在于,他们表示的都是使用具体类型的函数定义,而不是通用描述。
2.
调用常规模板生成具体类型的函数定义(模板实例)的过程是隐式实例化
调用显式具体化模板生成具体类型的函数定义(模板实例)的过程是显式具体化;前缀template<>
调用显式实例化模板生成具体类型的函数定义(模板实例)的过程是显式实例化;前缀template
3.同一数据类型显式具体化显式实例化不能共存;
4.显式具体化模板在main函数外面,显式实例化模板在main函数之内;
5.实例化是个过程,具体化是为某个具体类型提供(单独的属于自己的)函数定义;
6.具体化模板的实现,是在常规化模板的基础上实现的,即具体化模板不能单独存在。

一个简单的例子:非模板函数 优于 具体化模板 优于 常规模板

先看有没有非模板函数,有就直接调,
没有的话再看具体化模板,有调用具体化模板,
没有的话再看有没有常规模板,有的话就调用常规模板,没有的话就会报错,显示该函数调用出错。
(更具体的看8.5.5 编译器选择使用哪个函数版本

...
//常规模板:
template <class T>
void Swap(T &, T &);

//(显式)具体化模板:
template<> void Swap<job>(job &, job &);

int main(){
	//显式实例化模板:
	template void Swap<char>(char &, char &);//编译器使用模板来生成Swap()的char版本的模板实例
	
	int a,b;
	...
	Swap(a,b);//int类型的隐式实例化,调用最上面的常规模板
	
	job m,n;
	...
	Swap(m,n);//job类型的显式具体化,调用main函数上面声明的job类型的具体化模板,将使用为job类型提供的独立定义

	char x,y;
	...
	Swap(x,y);//char类型的显式实例化,调用main函数下面声明的char类型的显式实例化模板

	...
}

8.5.5 编译器选择使用哪个函数版本

提升转换 & 标准转换

提升转换:char 和 short 自动转换为 int ;float 自动转换为 double;
标准转换:int 转换为char ;long 转换为 double;

结论

简而言之,重载解析将寻找最匹配的函数:
如果只存在一个这样的函数,则选择它;
如果存在多个这样的函数,但其中只有一个是非模板函数,则选择该函数;非模板优于模板
如果存在多个适合的函数,且它们都为模板函数,但其中有一个函数比其他函数更具体,则选择该函数;具体化模板优于常规模板
如果有多个同样合适的非模板函数或模板函数,但没有一个函数比其他函数更具体,则函数调用将是不确定的,因此是错误的。如果不存在匹配的函数,则也是错误。更具体是指编译器推断使用哪种类型时执行的转换最少
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第10张图片

8.5.6 模板函数的发展—关键字decltype

假如有以下模板:

template <class T1, class T2>
void ft(T1 x, T2 y){
	...
	?type? xpy = x + y;
	...
}

考虑xpy是什么类型?
可能是T1、T2或者其他类型。
当T1是double型,T2是int型,x+y是double型,xpy是T1类型;
当T1是short型,T2是int型,x+y是int型,xpy是T2类型;
当T1时short型,T2是char型,x+y的结果自动整型提升,xpy是int型。

C++新增的关键字decltype提供了解决方案:

template <class T1, class T2>
void ft(T1 x, T2 y){
	...
	decltype(x + y) xpy = x + y;
	...
}

当使用关键字decltype时,为确定类型,编译器必须遍历一个核对表。假如有如下声明:

decltype(expression) var;

则核对表的简化版如下:
1.如果expression是一个没有用括号括起的标识符,则var的类型与该标识符的类型相同,包括const等限定符:

double x = 5.5;
double y = 7.9;
double& rx = x;
const double* pd;
decltype(x) w;		//w和x的类型相同,double
decltype(rx) u = y; //u和rx的类型相同,double&
decltype(pd) v;		//v和pd的类型相同,const double*

2.如果expression是一个函数调用,则var的类型与函数的返回类型相同:

long indeed(int);
decltype(indeed(3)) m;//m和indeed()函数的返回值类型相同,即long

3.如果expression是一个用括号括起来的标识符,则var为指向其类型的引用:

double xx = 4.4;
decltype ((xx)) r2 = xx;//r2是指向括号里的xx的引用,即double&
decltype (xx) w = xx;//w和xx的类型相同,即double

4.如果前面的条件都不满足,则var的类型与expression的类型相同:

int j = 3;
int& k = j
int& n = j;
decltype(j + 6) i1;//i1是int
decltype(100L) i2; //i2是long
decltype(k + n) i3;//i3是int

请注意,虽然k和n都是引用,但表达式k + n不是应用,他是两个int的和,所以i3int类型。

8.6 总结

8.1 内联函数
8.2 引用
8.2.6 继承
8.3 默认参数,即参数的默认值
8.4 函数重载(也叫函数多态)
8.5 函数模板(Template)—泛型编程

8.7 复习题

1. 哪种函数适合定义为内联函数?

通常情况下,程序设计中代码较简单的非递归函数可以使用内联函数。

2. 给函数原型设置默认参数,函数定义需要做什么修改?

设置默认参数后,
函数默认值只需要在声明处表示,在函数的定义处不需要标注参数默认值

6. 默认参数 和 函数重载(重点看题目b)

C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第11张图片
a.参数默认值和函数重载都能实现:
参数默认值方式如下:

double mass(double density, double volume = 1.0);

函数重载方式如下:

double mass(double density);
double mass(double density, double volume);

b.由于函数必须从右向左提供默认值,因此无法使用默认值,只能使用函数重载实现:

void repeat (int n,char* str);
void repeat (char* str);

c.题目中两个函数的参数类型不同,可以通过函数重载实现:

int average(int a,int b);
double average(double a,double b);

d.题目中函数的特征标相同,因此无法实现函数的重载。另外,由于返回的(字符’I’和指向字符串"I am glad to meet you"的)指针都是char*类型,因此返回值也相同。

补充:题目b的尝试

在这里插入图片描述
题目b的解读:
第一句:将指定的字符串显示指定次
第二句:将指定的字符串显示默认次,也就是5次。

我尝试给两个参数都设置一个默认值:

void repeat(int n = 5 ,const char* str = "123");

但是在调用的时候,
要么就两个参数都传入,这样可以实现第一句,即将指定的字符串显示指定的次数;
要么一个参数都不传入,直接使用两个默认参数,但这样就没办法实现第二句,即将指定的字符串显示默认(5)次

也就是说,不能只指定第二个参数,而使用第一个参数的默认值,因为函数必须从右向左提供默认值,换言之,如果要使用第一个默认参数,那它后面的所有参数都要使用默认值。

//第6题:
    repeat();
    repeat(3,"abc");
    //repeat("messi");//错误!不能只输入一个字符串而不输入次数

因此,题目b不能使用默认参数的方式实现,只能使用函数重载的方法实现。

8.实现一个模板的具体化

注意:要实现模板的具体化,一个首要前提是有一个模板。

因此,下面代码中的常规化模板

template<class T>
T& bigger(T& b1, T& b2);//模板函数的原型声明

是必不可少的,否则具体化模板

template<> box& bigger<box>(box& b1,box& b2);//模板的具体化声明

根本无法编译通过。

代码:

#include

using std::cout; using std::cin; using std::endl; using std::string;

//第8题:显式具体化要在原有模板函数的基础上,具体化特定类型的实现。
template<class T>
T& bigger(T& b1, T& b2);//模板函数的原型声明
struct box
{
    char maker[40];
    float height;
    float width;
    float length;
};
template<> box& bigger<box>(box& b1,box& b2);//模板的具体化声明
void showStruct(const box& b);

int main(){
//第8题:
    box b1 = {"China", 20.2, 30.3, 1.1};
    box b2 = {"indian", 2.2, 3.3, 0.1};
    showStruct(b1);
    showStruct(b2);
    cout << "比较大的box的信息为: " << endl;;
    box temp = bigger(b1,b2);
    showStruct(temp);
	return 0;
}

void showStruct(const box& b){
    cout << "maker:" << b.maker << "; height = " << b.height << "; width = " << b.width << ";length = " << b.length << endl;
}
template<class T>
T& bigger(T& b1, T& b2){//模板函数的实现
    return b1 > b2 ? b1 : b2;
}
template<> box& bigger<box>(box& b1,box& b2){//模板函数具体化的实现
    if(b1.length > b2.length)
        return b1;
    else    
        return b2;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第12张图片

9. decltype关键字

C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第13张图片
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第14张图片
本题的答案:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第15张图片

8.8 编程练习

第1题:默认参数 和 static变量

C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第16张图片
题目分析:
1.子函数的第二个参数设置为默认参数,默认值为0,当传入第二个(非0)参数的时候,默认值会被覆盖;
2.在子函数中设定一个静态值,用来存储函数被调用的次数static int count = 0;
函数内的静态变量能够在函数结束后仍保留在程序存储区内,并在一下次调用函数时,再一次被函数继续使用。
3.子函数中判断第二个参数是否为0,为0就打印一次,不为0就循环打印count次。

代码:

#include
#include

using std::cout; using std::cin; using std::endl; using std::string;

//第1题:
void print(const char* str,int n = 0);

int main(){
//第1题:
    print("123");
    print("456",10);//n != 0,此时print函数被调用了2次
    print("789");
    print("abc",1);//n != 0,此时print函数被调用了4次
    print("xyz");
    print("zyx",3);//n != 0,此时print函数被调用了6次

    return 0;
}
void print(const char* str,int n){
    static int count = 0;
    count++;
    if(n == 0){
        cout << "n == 0,打印 1 次:\n" << str << endl;
    }
    else{
        cout << "n != 0,打印 " << count << " 次:" << endl;
        for(int i = 0;i < count;i++){
            cout << str << " / ";
        }
        cout << endl;
    }
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第17张图片

static变量记录函数被调用的次数
#include

using std::cout; using std::cin; using std::endl;

int func1();//返回函数被调用的次数

int main(){
    func1();
    func1();
    func1();
    func1();
    func1();
    func1();
    func1();
    cout << "函数func1被调用的次数为:" << func1() << endl;
    
    return 0;
}

int func1(){
    static int count = 0;
    count++;
    return count;
}

结果:
函数func1被调用的次数为:8

第2题:char* 和 const char* 和 字符数组 和 string constant 和 字符串常量

先看题目2:

C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第18张图片

代码:

#include
#include

using std::cout; using std::cin; using std::endl; using std::string;

//第2题:
struct CandyBar
{
    char brandName[30];//1.这里要写字符数组,不能写char* brandName;
    double weight;
    int calorie;
};

void setCandyBar(CandyBar& c,const char* name = "Munch Box",const double weight = 2.85,const int calorie = 350);//2.这里要写const char* name = "Munch Box",不能写char* name = "Munch Box"
void showStrc(const CandyBar& c);


int main(){

//第2题:
    CandyBar c1;
    CandyBar c2;
    setCandyBar(c1);
    showStrc(c1);
    setCandyBar(c2,"wangzai",3.6,270);
    showStrc(c2);

    return 0;
}

void setCandyBar(CandyBar& c,const char* name,const double weight,const int calorie){
    strcpy(c.brandName,name);//3.学习strcpy()函数的原型和使用方法
    c.weight = weight;
    c.calorie = calorie;
}
void showStrc(const CandyBar& c){
    cout << "品牌名:" << c.brandName << "; 重量:" << c.weight << "; 热量:" << c.calorie << endl;
}

分析:
程序中的123点都涉及到strcpy()函数的使用,具体分析见下面的char* 和 const char* 和 字符数组 和 strcpy()函数

结果:
在这里插入图片描述

使用默认参数时在函数原型中设置默认值,函数定义的参数不要再写默认值了,否则会报错!!!
char* 和 const char* 和 字符数组 和 strcpy()函数

参考链接:strcpy()函数用法及其详解
strcpy()函数声明:

char *strcpy(char *dest, const char *src)

函数参数:

  • dest – 指向用于存储复制内容的目标数组char*要指向一个数组,不能指向一个char*,这个参数也不能是const char*类型。
  • src – 要复制的字符串,只能说const char*类型,不能是char*类型。

函数功能:

把 `src` 所指向的字符串复制到 `dest`。

返回值:

该函数返回一个指向最终的目标字符串 dest 的指针。

参数分析:
1.第一个参数dest是个char*类型,它指向一个字符数组,并且一个字符数组的位置和所占内存大小都是确定的。下面这样的写法是错误的

char *str;
strcpy(str,"The C of Tranquility");

strcpy()"The C of Tranquility"拷贝到str指向的地址上,但是str未被初始化,所以该字符串可能被拷贝到任意的地方。
因此得出上面的程序中的1.这里要写字符数组,不能写char* brandName;
这种写法就可以:

char *str = new char[50];
strcpy(str,"The C of Tranquility");

因为先new了一个内存块,并让str指向了这块内存,这样就可以把"The C of Tranquility"拷贝到str指向的内存块了。

2.第二个参数srcconst char*类型,它指向一个具体的字符串,也可以说用const char*可以接收字符串常量,而const char* 等价于 string ,因此可以将字符串常量赋给string或者const char*,将字符串常量赋给char*一定要记得加const
因此得出上面程序中的2.这里要写const char* name = “Munch Box”,不能写char*,因为如果写char*,编译器会提示“不能把const char*类型赋给char*类型”。

strcpy()的其它属性:
strcpy()的返回类型是char *,该函数返回的是一个指向最终的目标字符串 dest 的指针。第一个参数不必指向数组的开始,这个特性可用于拷贝数组的一部分

#include
#include

using std::cout; using std::cin; using std::endl; 

int main(){ 
    char str1[50] = "Be the best that you can be.";
    const char* str2 = "beast";
    cout << "str1 = " << str1 << endl;//str1 = Be the best that you can be.
    cout << "str2 = " << str2 << endl;//str2 = beast
    char* ps = str1 + 3;
    cout << "ps = " << ps << endl;//ps = the best that you can be.
    cout << endl;
    ps = strcpy(str1 + 7,str2);//把str2拷贝到str1 + 7的位置,返回值也是str1 + 7的位置
    cout << "str1 = " << str1 << endl;//str1 = Be the beast
    cout << "str2 = " << str2 << endl;//str2 = beast
    cout << "ps = " << ps << endl;//ps = beast 因为ps指向的不是str1,而是str1 + 7
        
    return 0;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第19张图片

const string 与const string&

参考链接:const string 与const string&(C++中的引用)
不带&的是一个常对象,带&是一个常引用,那么什么叫常引用呢?

在讲引用作为函数参数进行传递时,实质上传递的是实参本身,即传递进来的不是实参的一个拷贝,因此对形参的修改其实是对实参的修改,所以在用引用进行参数传递时,不仅节约时间,而且可以节约空间。

string constant 和 字符串常量

string constant 和 字符串常量 是一回事,一般的写法如下:

const char* name1 = "marco reus";//别忘了const
string name2 = "cristiano ronaldo";
char name3[50] = "lionel messi";
//char* pt1 = "karim benzema";//这样写是错的!
char* pt2 = name3;//这样写ok

第4题:字符数组 和 char*

C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第20张图片
代码:

#include
#include

using std::cout; using std::cin; using std::endl;
//第4题:
struct stringy
{
    char* pStr;//指向一个字符串
    int ct;//计算字符串的长度,不计算结束符'\0'
};
void set(stringy& str,const char* test);
void show(const stringy& str,int n = 1);
void show(const char* test,int n = 1);

int main(){
//第4题:
    stringy beany;
    char testing[] = "Reality isn't what it used to be.";
    set(beany,testing);//beany.pStr指向new的空间
    show(beany);cout << "--------" << endl; //这里还会用到beany.pStr
    show(beany,2);cout << "--------" << endl;//这里还会用到beany.pStr
    delete[] beany.pStr;//用完beany.pStr之后记得释放掉它指向的内存块
    
    testing[0] = 'D';
    testing[1] = 'u';
    show(testing);cout << "--------" << endl;
    show(testing,3);cout << "--------" << endl;
    show("Done!");
    return 0;
}
void set(stringy& str,const char* test){
    str.pStr = new char[strlen(test) + 1];//多new一个字节空间放结束符
    strcpy(str.pStr,test);
    str.ct = strlen(test);//字符个数,不算结束符  
}
void show(const stringy& str,int n){
    for(int i = 0;i < n;i++)
        cout << str.pStr << endl;//
    cout << "字符串中字符的个数为:" << str.ct << endl;
}
void show(const char* test,int n){
    for(int i = 0;i < n;i++){
        cout << test << endl;
    }
    cout << "字符串中字符的个数为:" << strlen(test) << endl;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第21张图片

注意1和2

1.注意delete[] beany.pStr;的时机,不能放到set函数中,否则后面在show()函数中调用str.pStr时,它指向的内存块已经被释放了。

2.直接对char*类型的指针str.pStr使用strcpy()函数,这次就是可以的,是因为new了一个内存块,并让str.pStr指向了这块内存:

	str.pStr = new char[strlen(test) + 1];//多new一个字节空间放结束符
    strcpy(str.pStr,test);//这里可以将test字符串copy给char*指针str.pStr,是因为上一行new了一个内存块,并让`str.pStr`指向了这块内存。

第6题:常规函数模板 和 具体化模板

C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第22张图片
题目分析:
1.模板具体化的前提是有个与之对应的常规模板,

template <class T>
T maxn(T arr[],int n);
template<> char* maxn<char*>(char* pArr[],int n);//具体类型是char* 即char指针,形参是char指针数组

2.题目中的具体化是为char*类型具体化,返回值也是char*类型,但参数是个char*数组,即char* arr[],然后自定义char*数组的时候,前面要加const,接收返回的char*时,也要加个const,因为const指针可以接收const和非const指针

	const char* pArr[5] = {"123","123456","12","1234","12345"};//别忘了const
    const char* maxStr = maxn(pArr,5);//用constchar指针来接收char*

否则会报如下的错误:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第23张图片

代码:

#include
#include

using std::cout; using std::cin; using std::endl; 
//第6题:
template <class T>
T maxn(T arr[],int n);
template<> char* maxn<char*>(char* pArr[],int n);//具体类型是char* 即char指针,形参是char指针数组

int main(){
//第6题:
    int arr1[6] = {20,15,-10,0,65,63};
    int max1 = maxn(arr1,5);
    cout << "max1 = " << max1 << endl;
    double arr2[4] = {-1.1,9.6,3.4,-26.1};
    double max2 = maxn(arr2,5);
    cout << "max2 = " << max2 << endl;

    const char* pArr[5] = {"123","123456","12","1234","12345"};//别忘了const
    const char* maxStr = maxn(pArr,5);//用constchar指针来接收char*
    cout << "最长的字符串为:" << maxStr << endl;

    return 0;
}

template <class T>
T maxn(T arr[],int n){
    T max = arr[0];
    for(int i = 1;i < n;i++){
        if(arr[i] > max)
            max = arr[i];
    }
    return max;
}
template<> char* maxn<char*>(char* pArr[],int n){
    int max = 0;
    char* maxStr;
    for(int i = 0;i < n;i++){
        if(strlen(pArr[i]) > max)
            max = strlen(pArr[i]);        
    }
    cout << "最长的字符串长度为" << max << endl;
    for(int i = 0;i < n;i++){
        if(strlen(pArr[i]) == max){
            maxStr = pArr[i];
            break;
        }  
    }
    return maxStr;
}

结果:
C++ Primer Plus(嵌入式公开课)---第8章 函数探幽_第24张图片

string 和 const char* 和字符串常量

const char*类型,它指向一个具体的字符串,也可以说用const char*可以接收字符串常量,而const char* 等价于 string ,因此可以将字符串常量赋给string或者const char*,将字符串常量赋给char*一定要记得加const

char* 和 字符数组 和 strcpy()函数

char*一般指向字符数组,如果它没指向字符数组,也要记得给它初始化!!!

//ok
char *pt1= new char[50];
strcpy(pt1,"The C of Tranquility");
//ok
char name[50];
char *pt2 = name;
strcpy(pt2,"The C of Tranquility");
//不ok:
char *pt3;//指针未初始化!!!
strcpy(pt3,"The C of Tranquility");

(第八章完)

第9章 内存模型和名称空间

点这里

你可能感兴趣的:(C++,Primer,Plus,c++,1024程序员节)