C++之内置函数、函数模板、变量的存储类别、类、析构函数、指针

来源:互联网 发布:翻墙原理 知乎 编辑:程序博客网 时间:2024/06/03 22:53

内置函数

C++提供一种提高效率的方法,即在编译时将所调用函数的代码直接嵌入到主调用函数中,而不是将流程转出去。这种嵌入到主调用函数中的函数称为内置函数(inline function)。

指定内置函数的方法很简单,只需在函数首行的左端加一个关键字inline即可。如:

#include <iostream>using namespace std;inline int max(int, int, int);  // 声明函数,注意左端有inlineint main(){int i = 10, j = 20, k = 30, m;m = max(i, j, k);cout << "max = " << m << endl;return 0;}inline int max(int a, int b, int c) // 定义max为内置函数{if (b > a) a = b;if (c > a) a = c;return a;}

由于在定义函数时指定它为内置函数,因此编译系统在遇到函数调用“max(i, j, k)”时,就用max函数体的代码代替它,同时将实参代替行参。这样main函数中的“m = max(i, j, k);”就被置换成了:

if (j > i) i = j;if (k > i) i = k;m = i;

注意:可以在声明函数和定义函数时同时写inline,也可以只在其中一处声明inline。

一般将规模很小(一般为5个语句以下)且使用频繁的函数(如定时采集数据的函数)声明为内置函数。在函数规模很小的情况下,函数调用的时间开销可能相当于甚至超过执行函数本身的时间,把它定义为内置函数,可大大减少程序运行时间。

内置函数中不能包括复杂的控制语句,如循环语句和switch语句。

函数模板

函数模板实际上是建立一个通用函数,其函数类型和行参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。如:

#include <iostream>using namespace std;template <typename T> // 模板声明,其中T为类型参数T max(T a, T b, T c) // 定义一个通用函数,用T做虚拟的类型名{if (b > a) a = b;if (c > a) a = c;return a;}int main(){int i = max(1, 2, 3); // 调用模板函数,此时T被int取代double j = max(1.2, 2.3, 3.5); // 调用模板函数,此时T被double取代cout << "i_max : " << i << endl;cout << "d_max : " << j << endl;return 0;}

template的含义是“模板”,尖括号中先写关键字typename,后面跟一个类型参数T,这个类型参数实际上是一个虚拟的类型名,表示模板中出现的T是一个类型名,但是现在并未指定它是哪一种具体的类型。

类型参数可以不只一个,可以根据需要确定个数,如:

template <typename T1, typename T2>

变量的存储类别

静态存储方式是指在程序运行期间,系统对变量分配固定的存储空间。

动态存储方式是指在程序运行期间,系统对变量动态地分配存储空间。

数据分别存放在静态存储区和动态存储区中。全局变量全部存放在静态存储区中,在程序开始执行时给全局变量分配存储单元,程序执行完毕就释放这些空间。在程序执行过程中它们占据固定的存储单元,而不是动态地进行分配和释放。

在动态存储区中存放以下数据:1.函数行参。在调用函数时给行参分配存储空间。2.函数中的自动变量。3.函数调用时的现场保护和返回地址等。

在C++中变量除了有数据类型的属性之外,还有存储类别的属性。存储类别指的是在内存中存储的方法。存储方法分为静态存储和动态存储两大类。具体包含4种:自动的(auto)、静态的(static)、寄存器的(register)和外部的(extern)。

自动变量:自动变量用关键字auto作存储类别的声明。变量默认都是自动变量,即auto int a = 3; 和int a = 3;作用相同。

用static声明静态局部变量:有时希望函数中的局部变量的值在函数调用结束后不消失而保留原值,即其占用的存储单元不释放,在下一次该函数调用时,该变量保留上一次函数调用结束时的值。这时就应该指定该局部变量为静态局部变量。如:

#include <iostream>using namespace std;int f(int a){int b = 0;static int c = 3; // 定义c为静态局部变量b += 1;c += 1;return a + b + c;}int main(){int a = 2;for (int i = 0; i < 3; i++)cout << f(a) << " ";cout << endl;return 0;}运行结果是: 7 8 9

静态局部变量在静态存储区内分配存储单元。在程序整个运行期间都不释放。不推荐使用静态局部变量。

用register声明寄存器变量:如果有一些变量使用频繁,且为存取变量的值要花不少时间。为提高执行效率,C++允许将局部变量的值放在CPU中的寄存器中,需要使用时直接从寄存器中取出参与运算,不必再到内存中去存取。关键字是register,如:register int i;不推荐使用。

用extern声明外部变量:全局变量(外部变量)是在函数的外部定义的,它的作用域为从变量的定义处开始,到本程序文件的末尾。在此作用域内,全局变量可以为本文件中各个函数所引用。编译时将全局变量分配在静态存储区。

有时需要用extern来声明全局变量,以扩展全局变量的作用域。

1.在一个文件内声明全局变量

如果外部变量不在文件的开头定义,其有效的作用范围只限于定义处到文件终了。如果在定义点之前的函数想引用该全局变量,则应该在引用之前用关键字extern对该变量作外部变量声明,表示该变量是一个将在下面定义的全局变量。如:

#include <iostream>using namespace std;int max(int, int);void main(){extern int a, b; // 对全局变量a, b作提前引用声明cout << max(a, b) << endl;}int a = 12, b = 13; // 定义全局变量a, bint max (int x, int y){int z;z = x > y ? x : y;return z;}运行结果是:13

2.在多文件的程序中声明外部变量

如果一个程序包含两个文件,在两个文件中都要用到同一个外部变量num,不能分别在两个文件中各自定义一个外部变量num,否则在进行程序的连接时会出现“重复定义”的错误。正确的做法是:在任一个文件中定义全局变量num,而在另一文件中用extern对num作外部变量声明。即:

extern int num;

编译系统由此知道num是一个已在别处定义的外部变量,它先在本文件中找是否有num,如果有,则将其作用域扩展到本行开始,如果本文件中没有,则在程序连接时从其他文件中找,如果有则把在另一个文件中定义的外部变量num的作用域扩展到本文件,在本文件中可以合法地引用该外部变量num。如:

file1.cppextern int a, b;int main(){count << a << ", " << b << endl;return 0;}file2.cppint a = 3, b = 4;

用extern扩展全局变量的作用域,虽然能为程序设计带来方便,但应十分慎重,因为在执行一个文件中的函数时,可能会改变了该全局变量的值,从而影响到另一文件中的函数执行结果。

用static声明静态外部变量:有时在程序设计中希望某些全局变量只限于被本文件引用,而不能被其他文件引用。这时可以在定义全局变量时加一个static声明。如:

file1.cppstatic int a = 3;int main(){...}file2.cppextern int a;int fun(int n){...a = a * n;}

在file1.cpp中定义了一个全局变量a,但它用static声明,故只能用于本文件,虽然在file2.cpp中用了“extern int a;”,但file2.cpp文件中仍然无法使用file1.cpp中的全局变量a。

这种加上static声明、只能用于本文件的全局变量称为静态全局变量。另外,不管有没有用static声明,全局变量都是用静态存储方式的。

内部函数

如果一个函数只能被本文件中的其他函数调用,则称之为内部函数。在定义内部函数时用关键字static修饰。如:

static int fun(int a, int b)
内部函数又称为静态函数,通常把只能由同一个文件使用的函数和全局变量放在一个文件中,在它们前面都用static修改使之局部化。

外部函数

外部函数用关键字extern修饰,可供其他文件调用。定义时省略extern则默认为外部函数。在需要调用此函数的文件中,用extern声明所调用的函数是外部函数。如:

file1.cpp#include <iostream>using namespace std;int main(){extern int max(int, int); // 声明在本函数中将要调用在其他文件中定义的max函数,extern也可省略int a, b;cin >> a >> b;cout << max(a, b) << endl;return 0;}file2.cppint max(int x, int y){return x > y ? x : y;}

类的声明

class 类名{private:私有的数据和成员函数;public:公用的数据和成员函数;};

如:

class Student{private:int num;char name[20];char sex;public:void display(){cout << "num : " << num << endl;}};

如果在类的定义中既不指定private也不指定public,则系统就默认为私有的。

在类外定义成员函数

如:

class Student{public:void display(); // 公用成员函数原型声明private:int num;string name;char sex;};void Student::display() // 在类外定义display函数{cout << "num : " << num << endl;cout << "name : " << name << endl;cout << "sex : " << sex << endl;}Student stud1,stud2; // 定义两个类对象

注意:成员函数在类外定义时,必须在函数名前加上类名予以限定,“::”是作用域限定符,用它声明函数是属于哪个类的。如果在作用域限定符的前面没有类名,或者函数名前面既没有类名也没有作用域限定符,则表示该函数不属于任何类,这个函数不是成员函数,而是全局函数。

类的封装性和信息隐蔽

由于在头文件包含了类的声明,因此在程序中就可以用该类来定义对象。由于在类体中包含了对成员函数的声明,在程序中就可以调用这些对象的公用成员函数。为了实现信息隐蔽,对类成员函数的定义一般不放在头文件中,而另放在一个文件中。如:

student.h 在此文件中进行类的声明class Student{public:void display(); // 公用成员函数原型声明private:int num;char name[20];char sex;};student.cpp 在此文件中进行函数的定义#include <iostream>#include "student.h"void Student::display() // 在类外定义display类函数{cout << "num : " << num << endl;cout << "name : " << name << endl;cout << "sex : " << sex << endl;}为了组成一个完整的源程序,还应当有包括主函数的源文件:main.cpp 主函数模块#include <iostream>#include "student.h"int main(){Student stud; // 定义对象stud.display(); // 执行stud对象的display函数return 0;}

析构函数

析构函数也是一个特殊的成员函数,它的作用与构造函数相反,它的名字是类名的前面加一个“~”符号。

当对象的生命期结束时,会自动执行析构函数。具体地说如果出现以下几种情况,程序就会执行析构函数:

1.如果在一个函数中定义了一个对象(它是自动局部对象),当这个函数被调用结束时,对象应该释放,在对象释放前自动执行析构函数。

2.static局部对象在函数调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用static局部对象的析构函数。

3.如果定义了一个全局对象,则在程序的流程离开其作用域(如main函数结束或调用exit函数)时,调用该全局对象的析构函数。

4.如果用new运算符动态地建立了一个对象,当用delete运算符释放该对象时,先调用该对象的析构函数。

析构函数的作用并不是删除对象,而是在撤销对象占用的内存之前完成一些清理工作。

析构函数不返回任何值,没有函数类型,也没有函数参数。

调用构造函数和析构函数的顺序:

一般情况下,先构造的后析构,后构造的先析构。它相当于一个栈,先进后出。

对象可以在不同的作用域中定义,可以有不同的存储类别。这些会影响调用构造函数和析构函数的时机。

1.在全局范围中定义的对象,它的构造函数在文件中的所有函数(包括main函数)执行之前调用。但如果一个程序中有多个文件,而不同的文件中都定义了全局对象,则这些对象的构造函数的执行顺序是不确定的。当main函数执行完毕或调用exit函数时,调用析构函数。

2.如果定义的是局部自动对象,则在建立对象时调用其构造函数。如果函数被多次调用,则在每次建立对象时都要调用构造函数。在函数调用结束、对象释放时先调用析构函数。

3.如果在函数中定义静态局部对象,则只在程序第一次调用此函数建立对象时调用构造函数一次,在调用结束时对象并不释放,因此也不调用析构函数,只在main函数结束或调用exit函数结束程序时,才调用析构函数。

指向对象的指针

对象空间的起始地址就是对象的指针。可以定义一个指针变量,用来存放对象的指针。

定义指向类对象的指针变量的一般形式为:

类名 *对象指针名;

如:

class Time{public:int hour;int minute;int sec;void get_time();};void Time::get_time(){cout << hour << ":" << minute << ":" << sec << endl;}

在此基础上有以下语句:

Time *pt; // 定义pt为指向Time类对象的指针变量Time t1; // 定义t1为Time类对象pt = &pt; // 将t1的起始地址赋给pt

这样,pt就是指向Time类对象的指针变量,它指向对象t1.

可以通过对象指针访问对象和对象的成员,如:

*pt                   pt所指向的对象,即t1.(*pt).hour            pt所指向的对象中的hour成员,即t1.hourpt->hour              pt所指向的对象中的hour成员,即t1.hour(*pt).get_time()      调用pt所指向对象中的get_time函数,即t1.get_time()pt->get_time()        调用pt所指向对象中的get_time函数,即t1.get_time()

指向对象成员的指针

对象有地址,存放对象初始地址的指针变量就是指向对象的指针变量。

对象中的成员也有地址,存放对象成员地址的指针变量就是指向对象成员的指针变量。

1.指向对象数据成员的指针

定义指向对象数据成员的指针变量的方法和定义指向普通变量的指针变量方法相同,如:

int *p1; // 定义指向整型数据的指针变量

定义指向对象数据成员的指针变量的一般形式为:

数据类型名 *指针变量名;

如果Time类的数据成员hour为公用的整型数据,则可以在类外通过指向对象数据成员的指针变量访问对象数据成员hour:

p1 = &t1.hour; // 将对象t1的数据成员hour的地址赋给p1,p1指向t1.hourcout << *p1 << endl; // 输出t1.hour的值

2.指向对象成员函数的指针

定义指向对象成员函数的指针变量的方法和定义指向普通函数的指针变量方法有所不同。

指向普通函数的指针变量的定义方法:

数据类型名 (*指针变量名)(参数列表);

如:

void (*p)(); // p是指向void型函数的指针变量

可以使它指向一个函数,并通过指针变量调用函数:

p = fun; // 将fun函数的入口地址赋给指针变量p,p就指向了函数fun(*p)(); // 调用fun函数

而定义一个执行对象成员函数的指针变量则比较复杂一点,如:

void (Time::* p2)(); // 定义p2为指向Time类中公用成员函数的指针变量

注意:(Time::* p2)两侧的括号不能省略,因为()的优先级高于*。如果无此括号,就相当于:

void Time::*(p2()); // 这是返回值为void型指针的函数

定义指向公用成员函数的指针变量的一般形式为:

数据类型名 (类名::* 指针变量名)(参数列表);

可以让它指向一个公用成员函数,只需把公用成员函数的入口地址赋给一个指向公用成员函数的指针变量即可。如:

p2 = &Time::get_time;

使用指针变量指向一个公用成员函数的一般形式为:

指针变量名 = &类名::成员函数名;

常对象

定义常对象的一般形式为:

类名 const 对象名[(实参列表)];  或  const 类名 对象名[(实参列表)];

如果一个对象被声明为常对象,则不能调用该对象的非const型的成员函数。

有时在编程时有要求,一定要修改常对象中的某个数据成员的值(如类中有一个用于计数的变量count),C++考虑到实际编程时的需要,对此做了特殊的处理,对该数据成员声明为mutable,如:

mutable int count;

把count声明为可变的数据成员,这样就可以用声明为const的成员函数来修改它的值。

指向对象的常指针

将指针变量声明为const型,这样指针值始终保持为其初值,不能改变。如:

Time t1(10, 12, 15), t2; // 定义对象Time * const ptr1; // const位置在指针变量名前面,规定ptr1的值是常值ptr1 = &t1; // ptr1指向对象t1,此后不能再改变指向ptr1 = &t2; // 错误,ptr1不能改变指向

定义指向对象的常指针的一般形式为:

类名 * const 指针变量名;

也可以在定义指针变量时初始化,如:

Time * const ptr1 = &t1; // 指定ptr1指向t1

往往用常指针作为函数的行参,目的是不允许在函数执行过程中改变指针变量的值,使其始终指向原来的对象。如果在函数执行过程中修改了该行参的值,编译系统就会发现错误,给出出错信息,这样比用人工来保证行参值不被修改更可靠。

指向常对象的指针变量

先了解指向常变量的指针变量,如:

const char * ptr; // 定义一个指向常变量的指针变量

注意const的位置在最左侧,它与类型名char紧连,表示指针变量ptr指向的char变量是常变量,不能通过ptr来改变其值的。

定义指向常变量的指针变量的一般形式为:

const 类型名 * 指针变量名;

指向常变量的指针变量可以指向const和非const型的变量,而指向非const型变量的指针只能指向非const的变量。

执行常对象的指针变量的概念和指向常变量的指针变量类似,只要将“变量”换成“对象”即可。

对象的复制(克隆)

其一般形式为:

类名 对象2(对象1);

用对象1复制出对象2.

在建立对象时调用一个特殊的构造函数--复制构造函数。函数形式为:

Box::Box(const Box& b){height = b.height;width = b.width;length = b.length;}

复制构造函数也是构造函数,但它只有一个参数,这个参数是本类的对象,而且采用对象的引用的形式。该复制构造函数的作用就是将实参对象的各成员值一一赋给新的对象中对应的成员。

继承

一般形式:

class 子类名 : [继承方式] 基类名{};

继承方式有:public、private、protected,如果不写默认是private的。

a+1和&a+1的区别:

首先a是一个数组名,当看到这个a与&a时,一般我们的理解都是这个数组的首地址。不过&a是整个数组的首地址,a则是数组首元素的地址,虽然值一样,但是意义却不相同。

0 0