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

0308

C++ Primer Plus - 第八章

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

点这里

第八章 函数探幽

8.1 C++内联函数

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

示例:

#include<iostream>

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

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

8.2 引用变量—references

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

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

8.2.1 创建引用变量

看个示例:

#include<iostream>

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. 引用的典型用途是作为函数参数,具体地说是结构体和(类)对象参数,即引用多用于指向结构体和类对象。

结果:
在这里插入图片描述
如果增加以下代码:

	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

结果是:
在这里插入图片描述
所以说rodentsrats是等价的,rodents = bunnies;就相当于rats = bunnies;
rodentsrats的地址一直相同,内容也一直相同。

8.2.2 将引用用作函数参数

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

示例:

#include<iostream>

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<iostream>

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也发生了变化。

结果:
在这里插入图片描述
如果不想影响到实参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<iostream>
#include<string>

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的引用

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

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

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

8.2.6 对象、继承和引用

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

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

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

看示例:

#include<iostream>
#include<fstream>
#include<cstdlib>
#include<string>

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<iostream>
#include<string>

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<iostream>
#include<string>
#include<cstring>

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

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

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 <class T>
void swap(T& a, T& b);//函数原型

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

模板函数的定义

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

示例:

#include<iostream>
#include<string>
#include<cstring>

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

//建立一个模板函数:
template <typename T>
//template <class T>
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 <typename T>
template <class T>
void swap(T& a, T& b){
	T temp;
	temp = a;
	a = b;
	b = temp;
}

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

分析:

  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<iostream>

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

//交换两个数的模板:
template <typename T>
//template <class 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);

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

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

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<iostream>
#include<string>
#include<cstring>

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

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

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;

结论

简而言之,重载解析将寻找最匹配的函数:
如果只存在一个这样的函数,则选择它;
如果存在多个这样的函数,但其中只有一个是非模板函数,则选择该函数;非模板优于模板
如果存在多个适合的函数,且它们都为模板函数,但其中有一个函数比其他函数更具体,则选择该函数;具体化模板优于常规模板
如果有多个同样合适的非模板函数或模板函数,但没有一个函数比其他函数更具体,则函数调用将是不确定的,因此是错误的。如果不存在匹配的函数,则也是错误。更具体是指编译器推断使用哪种类型时执行的转换最少
在这里插入图片描述

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)

在这里插入图片描述
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<iostream>

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

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

9. decltype关键字

在这里插入图片描述
在这里插入图片描述
本题的答案:
在这里插入图片描述

8.8 编程练习

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

在这里插入图片描述
题目分析:
1.子函数的第二个参数设置为默认参数,默认值为0,当传入第二个(非0)参数的时候,默认值会被覆盖;
2.在子函数中设定一个静态值,用来存储函数被调用的次数static int count = 0;
函数内的静态变量能够在函数结束后仍保留在程序存储区内,并在一下次调用函数时,再一次被函数继续使用。
3.子函数中判断第二个参数是否为0,为0就打印一次,不为0就循环打印count次。

代码:

#include<iostream>
#include<string>

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

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

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

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:

在这里插入图片描述

代码:

#include<iostream>
#include<string.h>

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<iostream>
#include<string.h>

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

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

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*

在这里插入图片描述
代码:

#include<iostream>
#include<string.h>

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

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

注意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题:常规函数模板 和 具体化模板

在这里插入图片描述
题目分析:
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*

否则会报如下的错误:
在这里插入图片描述

代码:

#include<iostream>
#include<string.h>

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

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

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章 内存模型和名称空间

点这里

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值