【Essential C++学习笔记】第二章 面向过程的编程风格

第二章 面向过程的编程风格

将函数独立出来的做法可带来三个主要好处:第一,以一连串函数调用操作,取代重复撰写相同的程序代码,可使程序更容易读懂。第二,我们可以在不同的程序中使用这些函数。第三,我们可以更轻易地将工作分配给协力开发团队。

2.1 如何撰写函数

函数必须先被声明,然后才能被调用(被使用)。函数的声明让编译器得以检查后继出现的使用方式是否正确----是否有足够的参数,参数型别是否正确等等。函数声明不必提供函数的主体部分,但必须指明返回型别、函数名称,以及参数表.此即所谓的函数原型

函数参数的三种传递方式——值传递、指针传递、引用传递

在函数参数中一会用 *,一会用&,有点莫名其妙,下面看看是什么个情况

(1)值传递
将已经初始化的变量值(或常量)传递到函数中。

int func(int value)
{
    int ret = value++;
    return ret;
}
//调用函数时,实参需要先进行初始化
int num = 2;
func(num);

值传递是将实参的值赋值给了形参,形如上例中,实际上是:int value; value=num;,所以实参必须要先进行初始化。

另外,因为只是赋值,所以形参value值的改变,并不会影响实参num的值

(2)指针传递
对于指针传递来说,传递的是指针变量,也是值传递(此时值是指针),是值传递就必须先进行初始化。

1)形参未被初始化的情况(不被允许)

int func(int *value)
{
    value = (int*)malloc(sizeof(int));
}
int *p ;//仅定义了一个指针,而未进行初始化
func(p); 

由上可见,这种情况下,并不能对实参p进行赋值,因为,这实际上是 值传递(传递的值是指针),而值传递,实参必须先初始化,所以,这种情况是不允许的。但是,指针传递,实参指向的变量可以是未初始化的,即调用函数可以对实参指向的变量进行赋值。这是因为传递的是该变量的地址,所以是有值传递。

 // 情况1:实参指向变量为一般变量
int func(int *value)
{    
    *value =2;
    int ret = (*value)++;
    return ret;
}
 
int num;//变量未进行初始化
int *p = #//p为实参,已进行初始化。
func(p); 

// 情况2:实参指向变量为指针变量。
int func(int **value)
{
    *value = (int*)malloc(sizeof(int));
}
 
int *p ;//仅定义了一个指针,而未进行初始化
int **q= &p;
func(q); 

2)实参已被初始化的情况:

//情况1:实参指向的变量被初始化,但实参p已被初始化 
int func(int *value)
{
    int ret = (*value)++;
    return ret;
}
 
int num = 2;    
int *p = #//p为实参,已进行初始化。
func(p); //num的值增加1

// 情况2:实参指向的变量没有被初始化,但实参p已被初始化 (存储的是所指向变量的地址)
int func(int *value)
{    
    *value =2;
    int ret = (*value)++;
    return ret;
}
 
int num;//变量未进行初始化
int *p = #//p为实参,已进行初始化。
func(p); 

(3)引用传递

传递引用就是传递实参本身,此时,实参可以是已初始化,也可以是未初始化的值,引用传递适用于变量中含有的数据很大,传递引用就无需再赋值给形参(相较于值传递)

// 情况1:非指针实参变量未初始化:
int func(int &value)
{
    value = 2//此处对实参p进行了赋值
    int ret = value++;
    return ret;
}
int p ;//仅定义了变量,而未进行初始化
func(p); //p的值为3

// 情况2:非指针实参变量已初始化:
int func(int &value)
{  
    int ret = value++;
    return ret;
}
int p = 3;//直接对实参进行初始化
func(p); //执行后p的值为4

// 情况3:指针实参且未初始化的情况:
void func(int* &value)
{
    value = (int*)malloc(sizeof(int)*2);//此处对实参p进行了赋值
   
}
int *p ;//仅定义了变量,而未进行初始化
func(p); 

// 情况4:指针实参且已初始化的情况:
void func(int* &value)
{
    value[0] = 1;//此处对实参p进行了赋值
}
int *p  = (int*)malloc(sizeof(int)*2);//实参初始化
func(p); 

2.2 取地址运算符 ( & ) 和 间接寻址运算符 ( * )

& 返回操作数的内存地址。如果 var 是一个整型变量,则 &var 是它的地址。

*****返回操作数所指定地址的变量的值。

声明时,变量前加 “&” :声明引用变量。它是某个已存在变量的别名,即该引用变量名称与原始变量名称都代表同一个变量。

声明时,变量前加 “*” :声明指针变量。它的值是另一个变量的地址

声明时,变量前加 “*”:声明二级指针变量。它的值是另一个一级"基本类型 *"指针变量的地址 (指针的指针)。

调用时,变量前加 “&” :使用取地址运算符获取该变量的地址

调用时,指针变量前加 “*” :使用间接寻址运算符获取该指针变量所指向的变量

调用时,二级指针变量前加 “**” :获取该二级指针变量所指向的指针所指向的变量

#include <iostream>
using namespace std; 
int main(){
    int  var;     // 声明int类型变量var
    int * ptr;    // 声明指针变量ptr
    ptr = &var;   // 先使用 & 运算符获取变量var的地址,再把该地址赋值给指针变量ptr
    int ** pptr;  // 声明二级指针变量pptr
    pptr = &ptr;  // 先使用 & 运算符获取变量ptr的地址,再把该地址赋值给二级指针变量pptr
    int & ref1 = var;   // 声明引用变量ref1, ref1是变量var的别名(引用必须在创建时被初始化)
    int & ref2 = *ptr;  // 先使用*运算符获取指针变量ptr所指向的变量(即var),再用该变量(var)初始化引用变量ref2(声明引用变量ref2的同时对它进行初始化)。也就是说,该行代码执行后,ref2也是变量var的别名 var = 20
    cout << "Value of var: "; 
    cout << var << endl; 
    cout << "Value of &var: "; 
    cout << &var << "\t(var的地址)" << endl; 
    cout << endl; 
    cout << "Value of ptr: "; 
    cout << ptr << "\t(等于&var)" << endl; 
    cout << "Value of *ptr: "; 
    cout << *ptr << "\t\t(等于var)" << endl; 
    cout << "Value of &ptr: "; 
    cout << &ptr << "\t(ptr的地址)" << endl; 
    cout << endl; 
    cout << "Value of pptr: "; 
    cout << pptr << "\t(等于&ptr)" << endl; 
    cout << "Value of *pptr: "; 
    cout << *pptr << "\t(等于ptr, 等于&var)" << endl; 
    cout << "Value of **pptr: "; 
    cout << **pptr << "\t\t(等于*ptr, 等于var)" << endl; 
    cout << "Value of &pptr: "; 
    cout << &pptr << "\t(pptr的地址)" << endl; 
    cout << endl; 
    cout << "Value of ref1: "; 
    cout << ref1 << "\t\t(等于var)" << endl; 
    cout << "Value of &ref1: "; 
    cout << &ref1 << "\t(等于&var)" << endl; 
    cout << endl; 
    cout << "Value of ref2: "; 
    cout << ref2 << "\t\t(等于var)" << endl; 
    cout << "Value of &ref2: "; 
    cout << &ref2 << "\t(等于&var)" << endl; 
    return 0; 
}

2.3 左值右值的理解

左值表示了一个占据内存中某个可识别的位置(也就是一个地址)的对象
右值不表示内存中某个可识别位置的对象的表达式。

int var;
var = 4; //其中 var 是一个有内存位置的对象,因此它是左值

取地址操作符 '&' 需要一个左值参数,返回一个右值:

int var = 10;
int* bad_addr = &(var + 1); // 错误: 一元 '&' 操作符需要左值参数
int* addr = &var;           // 正确: var 是左值
&var = 40;                  // 错误: 赋值操作的左操作数需要是左值

2.4 提供默认参数值

第一个规则是,默认值的决议(resolve)操作由最右边开始进行.如果我们为某个参数提供了默认值,那么这个参数右侧的所有参数都必须也具有默认参数值才行
第二个规则是,默认值只能够指定一次,可以在函数声明处,亦可以在函数定义处,但不能够在两个地方都指定。那么、我们应该在何处指定参数的默认值呢?

#include <fstream>
#include <iostream>
#include <vector>
//	默认使用cout输出,当代码为display(vec,ofil); 表示输出到文件上
void display ( vector<int> vec, ostream &os = cout) {
	for (int i = 0; i < vec.size(); ++i) {
		os << vec[i] << ' ';
	}
	cout << endl;
}

static关键字

生命周期与作用域

• 生存周期: 变量从定义到销毁的时间范围。存放在全局数据区的变量的生存周期存在于整个程序运行期间,而存放在栈中的数据则随着函数等的作用域结束导致出栈而销毁,除了静态变量之外的局部变量都存放于栈中。
• 作用域: 变量的可见代码域(块作用域,函数作用域,类作用域,程序全局作用域)。

C++的static有两种用法:面向过程程序设计中的static和面向对象程序设计中的static。前者应用于普通变量和函数,不涉及类;后者主要说明static在类中的作用。

一、面向过程设计中的static

static变量是指静态的变量,不管是在全局还是局部声明的static变量都存放于程序的全局变量区域,所以它的生命周期是从程序开始到程序结束。但是static变量的作用域并不等同于它的生存周期,它的作用域决定于它被定义的位置。

可以认为静态变量 的作用域<=生存周期

案例1:静态局部变量

#include <iostream>
using namespace std;
void fn();
int main() {
	fn();  // 10
	fn();  // 11
	fn();  // 12
	return 0;
}

void fn() {
	static int n = 10; // 第二次运行,不会再次进行初始化
	cout << n << endl;
	n++;
}

• 该变量在全局数据区分配内存;
• 静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
• 静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
• 它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;

案例2:静态全局变量

//File1
#include <iostream> 
using namespace std;

int k = 10; //定义全局变量,其他文件引用用extern
static int n = 20; //定义静态全局变量

int main() {
	cout << k << endl;
	cout << n << endl;
	return 0;
}

全局变量和静态变量j都存放于程序的全局数据区域,它们的生存周期都是程序的整个运行期,但是n的作用域为全局作用域,可以通过extern在其他文件中使用,而j只能在文件A中使用,例如在文件B中:

//File2
#include <iostream>
using namespace std;

extern int k;//ok
extern int n;//error: n在文件B中不可见

int main() {
	cout << k << endl;
	cout << n << endl;
	return 0;
}

案例3:静态函数
在函数的返回类型前加上static关键字,函数即被定义为静态函数。静态函数与普通函数不同,它只能在声明它的文件当中可见,不能被其它文件使用。

定义静态函数的好处:
• 静态函数不能被其它文件所用;
• 其它文件中可以定义相同名字的函数,不会发生冲突;

#include <iostream.h>
static void fn();//声明静态函数
void main()
{
  fn();
}
void fn()//定义静态函数
{
  int n=10;
  cout<<n<<endl;
}

静态全局变量 vs 全局变量

生存周期作用域引用方法
static全局变量从程序开始到程序结束被定义的文件本文件直接引用,其他文件无法引用
static局部变量从程序开始到程序结束局部作用域局部直接引用,其他作用域无法使用
全局变量从程序开始到程序结束全局作用域(只需要在一个源文件中定义,就可以作用于所有的源文件)其他文件用extern 等关键字声明要引用的全局变量

总结:
全局变量、局部变量、全局静态变量、局部静态变量的区别。要从分配内存的位置和作用域入手来解释。

全局变量,分配的内存在静态存储区内存上面,其作用域是全局作用域,也就是整个程序的生命周期内都可以使用,同时,有些程序并不是由一个源文件构成的,可能有许多个源文件构成,全局变量只要在一个文件中定义,就可以在其他所有的文件中使用,当然,必须在其他文件使用extern关键字声明该变量。

局部变量,分配内存是分配在栈存储区上的,其作用域也只是在局部函数内,在定义该变量的函数内,只要出了该函数,该局部变量就不再起作用,该变量的生命周期也只是和该函数同在。

全局静态变量,分配的内存与全局变量一样,也是在静态存储内存上,其生命周期也是与整个程序同在的,从程序开始到结束一直起作用,但是与全局变量不同的是,全局静态变量作用域只在定义它的一个源文件内,其他源文件不能使用它。

局部静态变量,分配的内存也是在静态存储内存上的,其第一次初始化后就一直存在直到程序结束,该变量的特点是其作用域只在定义它的函数内可见,出了该函数就不可见了

二、面向对象的static关键字(类中的static关键字)
2.1静态数据成员
在类内数据成员的声明前加上关键字static,该数据成员就是类内的静态数据成员。

#include <iostream>
using namespace std;

class Myclass {
	public:
		Myclass(int a, int b, int c);
		void GetSum();
	private:
		int a, b, c;
		static int Sum;//声明静态数据成员
};
int Myclass::Sum = 0; //定义并初始化静态数据成员

Myclass::Myclass(int a, int b, int c) {
	this->a = a;
	this->b = b;
	this->c = c;
	Sum += a + b + c;
}

void Myclass::GetSum() {
	cout << "Sum=" << Sum << endl;
}

int main() {
	Myclass M(1, 2, 3);
	M.GetSum();
	Myclass N(4, 5, 6);
	N.GetSum();
	return 0;
}

• 对于非静态数据成员,每个类对象都有自己的拷贝。而静态数据成员被当作是类的成员。无论这个类的对象被定义了多少个,静态数据成员在程序中也只有一份拷贝,由该类型的所有对象共享访问。也就是说,静态数据成员是该类的所有对象所共有的。对该类的多个对象来说,静态数据成员只分配一次内存,供所有对象共用。所以,静态数据成员的值对每个对象都是一样的,它的值可以更新;
• 静态数据成员存储在全局数据区。静态数据
成员定义时要分配空间
,所以不能在类声明中定义。
静态数据成员主要用在各个对象都有相同的某项属性的时候。比如对于一个存款类,每个实例的利息都是相同的。所以,应该把利息设为存款类的静态数据成员。这有两个好处,第一,不管定义多少个存款类对象,利息数据成员都共享分配在全局数据区的内存,所以节省存储空间。第二,一旦利息需要改变时,只要改变一次,则所有存款类对象的利息全改变过来了;
• 同全局变量相比,使用静态数据成员有两个优势:

  1. 静态数据成员没有进入程序的全局名字空间,因此不存在与程序中其它全局名字冲突的可能性;
  2. 可以实现信息隐藏。静态数据成员可以是private成员,而全局变量不能;

2.2静态成员函数

与静态数据成员一样,我们也可以创建一个静态成员函数,它为类的全部服务而不是为某一个类的具体对象服务。

静态成员函数与静态数据成员一样,都是类的内部实现,属于类定义的一部分。普通的成员函数一般都隐含了一个this指针,this指针指向类的对象本身,因为普通成员函数总是具体的属于某个类的具体对象的。

通常情况下,this是缺省的。如函数fn()实际上是this->fn()。但是与普通函数相比,静态成员函数由于不是与任何的对象相联系,因此它不具有this指针。从这个意义上讲,它无法访问属于类对象的非静态数据成员,也无法访问非静态成员函数,它只能调用其余的静态成员函数。

#include <iostream>
using namespace std;

class Myclass {
	public:
		Myclass(int a, int b, int c);
		static void GetSum();
		// 声明静态成员函数
	private:
		int a, b, c;
		static int Sum;//声明静态数据成员
};
int Myclass::Sum = 0; //定义并初始化静态数据成员

Myclass::Myclass(int a, int b, int c) {
	this->a = a;
	this->b = b;
	this->c = c;
	Sum += a + b + c; //非静态成员函数可以访问静态数据成员
}

void Myclass::GetSum() { //静态成员函数的实现
// cout<<a<<endl; //错误代码,a是非静态数据成员
	cout << "Sum=" << Sum << endl;
}


int main() {
	Myclass M(1, 2, 3);
	M.GetSum();
	Myclass N(4, 5, 6);
	N.GetSum();
	return 0;
}

• 出现在类体外的函数定义不能指定关键字static;
• 静态成员之间可以相互访问,包括静态成员函数访问静态数据成员和访问静态成员函数;
• 非静态成员函数可以任意地访问静态成员函数和静态数据成员;
• 静态成员函数不能访问非静态成员函数和非静态数据成员;
• 由于没有this指针的额外开销,因此静态成员函数与类的全局函数相比速度上会有少许的增长;

2.5 声明一个lnline函数

• 将函数声明为inline,表示要求编译器在每个函数调用点上将函数的内容展开。编译器将该函数的调用操作改为用一份函数代码副本代替,使我们的性能改善,其结果等于把三个函数写入fibon_elem()内,但仍然维持着三个独立的运算单元
inline仅仅是对编译器的一种请求而没有强制性,是否执行需视编译器而定。
• 最适合声明为inline的函数:体积小,常被调用,所从事的计算并不复杂。
inline函数的定义常被放在头文件中,因为编译器要在它被调用的时候加以展开,此时其定义必须有效。
C++中inline的用法——转自博客园Boblim的博客

2.6 函数重载

1.函数的重载的规则:

  • 函数名称必须相同。
  • 参数列表必须不同(个数不同、类型不同、参数排列顺序不同等)。
  • 函数的返回类型可以相同也可以不相同。
  • 仅仅返回类型不同不足以成为函数的重载

2.C++ 是如何做到函数重载的

C++代码在编译时会根据参数列表对函数进行重命名,例如void Swap(int a, int b)会被重命名为_Swap_int_intvoid Swap(float x, float y)会被重命名为_Swap_float_float。当发生函数调用时,编译器会根据传入的实参去逐个匹配,以选择对应的函数,如果匹配失败,编译器就会报错,这叫做重载决议(Overload Resolution)。

不同的编译器有不同的重命名方式,这里仅仅举例说明,实际情况可能并非如此。

从这个角度讲,函数重载仅仅是语法层面的,本质上它们还是不同的函数,占用不同的内存,入口地址也不一样。

2.7 函数模板(template)

所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体指定,用一个虚拟的类型来代表。这个通用函数就称为函数模板。

function template以关键字template开场,其后紧接成对尖括号<>包围起来的一个或多个标识符,用以表示我们希望推迟决定的数据类型。这些标识符扮演占位符的角色,用来放置函数参数列表及函数体中的某些实际数据类型,用户每次利用这一模板产生函数,都必须提供确实的类型信息。

凡是函数体相同的函数都可以用这个模板来代替,不必定义多个函数,只需在模板中定义一次即可。在调用函数时系统会根据实参的类型来取代模板中的虚拟类型,从而实现了不同函数的功能。

函数模板定义形式
由以下三部分组成: 模板说明 + 函数定义 + 函数模板调用

template < 类型形式参数表 >
类型 函数名 (形式参数表)
{
//语句序列
}

详见:https://blog.youkuaiyun.com/m0_53636439/article/details/119777817

2.8 函数指针

对于变量我们可以用int *a这样的语法创建一个指针,如果我们想写一个指向函数的指针我们可以这么写: 类型 ( * name) ();

  1. 把一个函数赋值给函数指针,并且调用:
#include <iostream>
using namespace std;

int foo() {
	return 5;
}

int goo() {
	return 6;
}

int main() {
	int (* pointFunc)() = foo;	//funcPtr 现在指向了函数foo
	cout << pointFunc() << endl;
	pointFunc = goo;			// pointFunc 现在又指向了函数goo
	cout << pointFunc() << endl;
	return 0;
}

2.把函数作为参数传入另一个函数

例一:

#include <iostream>
using namespace std;

int add(int a, int b) {
	return a + b;
}

int sub(int a, int b) {
	return a - b;
}

// 传入了一个int型,双参数,返回值为int的函数f
void func(int k, int v, int(*f)(int a, int b)) {
	cout << f(k, v) << endl;
}

int main() {

	func(2, 3, add);
	func(2, 3, sub);
	return 0;
}

例二:简单的冒泡排序(可切换升序降序)

#include <iostream>
using namespace std;

template <typename T> // 可变类型T

bool upSort(T x, T y) {
	return x > y;
}
template <typename T> // 可变类型T

bool downSort(T x, T y) {
	return x < y;
}

template <typename T> // 可变类型T

// a:原数组  n:数组长度  cmpfunc
void bubblesort(T *a, int n, bool(*cmpfunc)(T, T)) {
	for (int i = 0; i < (n - 1); ++i) {
		for (int j = i + 1; j < (n - 1); ++j) {
			if (cmpfunc(a[i], a[j + 1])) {
				swap(a[i], a[j + 1]);
			}
		}
	}
}


int main() {

	int a[8] = {5, 2, 5, 7, 1, -3, 99, 56};
	int b[8] = {5, 2, 5, 7, 1, -3, 99, 56};

	bubblesort<int>(a, 8, upSort);
	for (auto e : a)
		cout << e << " ";
	cout << std::endl;

	bubblesort<int>(b, 8, downSort);
	for (auto e : b)
		cout << e << " ";
	cout << std::endl;

	return 0;
}

可以设置默认函数

//设置默认函数
void bubblesort(T *a, int n, bool(*cmpfunc)(T, T) = upSort)

关于函数指针和函数指针数组的定义及更多相关内容请看该文章。写的很好
函数指针和函数指针数组及其应用——whuTommy的优快云博客

2.9 设置头文件

  • 假如为了不用分别在五个文件中声明seq_elem(),我们把函数声明可以放在头文件中,然后每个程序代码文件(.cpp)文件内#include" "头文件来包含这些函数声明,本意还是为了方便调用一些(自定义)函数,然后也方便修改函数的参表或者返回类型,包含的头文件会自动更新掉。
  • 我们可以 将函数声明连带函数体写入到一个.h文件中,也可以仅在.h头文件中写函数声明,将函数主体放在一个或多个.c文件中。
  • 头文件中常放函数的声明,变量的声明,而不能放它们的定义
  • 为了防止重复编译,在开头和结尾处必须按照如下样式加上预编译语句:“ #ifndef XXX #define XXX 。。。#endif"
//printhello.h
#ifndef PRINTHELLO_H_
#define PRINTHELLO_H_
void printhello();

#endif

// printhello.cpp
#include"printhello.h"
void printhello(){
	std::cout << "Hello!\n";
}

//main.cpp
//  调用刚刚创建的头文件,用“”(标准库的用<>),然后使用头文件里面的函数printhello():

#include "printhello.h"
int main() {
	printhello();
     return 0;
}

  • 双引号""与尖括号<>包含的区别:
  • 如果头文件和包含此文件的程序代码文件位于同一个磁盘目录下,便使用双引号;如果在不同的目录下,便使用尖括号
  • 如果此文件被认定为标准的或项目专属的头文件,便使用尖括号,编译器会先在某些默认的磁盘目录中寻找;如果文件名由双引号括住,该文件被认为是用户提供的头文件,搜索时从要包含此文件的程序文件所在的磁盘目录开始找起。

关于头文件相关的可以看看下面这个文档进行理解:
函数头文件:https://blog.youkuaiyun.com/Leeoo_lyq/article/details/105739741?spm=1001.2101.3001.6650.1&utm_medium=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-105739741-blog-83017491.235%5Ev38%5Epc_relevant_default_base&depth_1-utm_source=distribute.pc_relevant.none-task-blog-2%7Edefault%7ECTRLIST%7ERate-1-105739741-blog-83017491.235%5Ev38%5Epc_relevant_default_base&utm_relevant_index=2
类头文件:https://zhuanlan.zhihu.com/p/476285979

要点:

  • 每个函数必须定义返回类型函数名参数列表函数体四个部分。
  • 函数必须先被声明才能被调用(使用)。
  • 函数的声明让编译器得以检查后续出现的使用方式是否正确(参数是否足够、参数类型是否正确等)
  • 函数声明不必提供函数体,但必须指明返回类型、函数名和参数列表,即所谓的函数原型(function prototype)
  • 标准库的exit()函数用来终止程序。必须传一个值作为程序结束时的状态值。
  • 每个值类型都只能表示其值域(domain)中的最小至最大值间的某个值,超出即发生溢出(overflow)。
//查询某个类型的最小/最大值,
#include <limits>
int max_int = numeric_limits<int>::max();
double min_dbl = numeric_limits<double>::min(); 
  • 两种参数传递方式:传址(by reference)和传值(by value)。
  • 当我们调用一个函数时,会在内存中建立起一块特殊区域,称为程序堆栈(program stack),提供了每个函数参数的储存空间,也提供了函数所定义的每个对象的内存空间(这些对象也叫局部对象local object)。一旦函数完成,这块内存就会被释放,也就是从程序堆栈中被pop出来。
  • 将对象传入函数,默认情形下它的值会被复制一份,成为参数的局部定义。这种方式称为传值
  • 让参数和传入的实际对象产生关联,即传址,最简单的方法便是将参数声明为一个引用(reference):
void swap(int &val1, int & val2)
{
	int temp = val1;
	val1 = val2;
	val2 = temp;
}
  • 将参数声明为reference的理由有:
  • 可以直接对传入的对象进行修改;
  • 降低复制大量对象的额外负担(直接传入vector的地址比复制其中的所有元素更快)。
  • 如下代码所示,我们声明了一个reference to const vector,因为函数之中并不会更改vector的内容。加上const可以让阅读程序的人了解,我们以传址的方式传递vector是为了避免复制操作,而不是为了要在函数中对它进行修改
void display( const vector<int> &vec )
{
	for( int ix = 0; ix < vec.size(); ++ix)
		cout << vec[ix] << ' ';
	cout << endl;
}
  • pointer可能(也可能不)指向某个实际对象,当我们提领pointer时,一定要先确定其值并非0;而reference必定会代表某个对象,所以不需做此检查。
  • 默认参数值需遵循两个规则:
  • 默认值的解析(resolve)操作由最右边开始进行。所以有默认值的参数右侧的所有参数都必须有默认值:
    void display(ostream &os = cout, const vector<int> &vec);错误
  • 默认值只能够指定一次,函数声明处或函数定义处。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值