C++函数

先记录一些零碎的内容

C++为类型建立别名的方式有两种:

预处理器:在编译时用char替换所有的BYTE,从而使BYTE成为char的别名

    #define BYTE char
    #define BYTE_POINTER char*
    BYTE_POINTER p1, p2;
    // char *p1, p2; 

typedef:可以处理更复杂的类型的别名,不会出现上面pointer的问题,typedef不会创建新类型,知识为已有的类型建立一个新的名称

    typedef typeName aliasName;
    typedef char BYTE;
    typedef char * BYTE_POINTER;

1、函数参数与按值传递

C++通常按值传递函数参数:函数被调用时,会创建一个新的变量,并将其初始化,这样就不会影响到外部的数据。用于接收传递值的变量被称为形参(parameter),传递给函数的值被称为实参(argument)

2、给函数传递一维数组

将数组作为参数意味着将数组的位置、元素的类型提交给函数,此时函数仍按值传递,只不过是传递的一个地址,所以传递数组时要附加数组长度。

    int sum_arr(int arr[], int n)
    {
        cout << sizeof(arr) << endl;    // 4
	    int sum = 0;
	    for (int i = 0; i < n; i++)
	    {
	    	sum += arr[i];
            arr[i] += 1;
	    }
	    return sum;
    }
 
	int arr[]{ 1,2,3,4,5 };
	int sum = sum_arr(arr, sizeof(arr));

    for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
		cout << arr[i] << " ";	// 2 3 4 5 6

由于实际传递的是一个地址,在函数内部对数组使用sizeof返回的是指针的大小,此外在函数内修改数组内元素的值时,会影响到外部的实参。

如果我们并不想函数能够修改我们传递进去的内容,那么我们可以用const来修饰形参:

int sum_arr2(const int *arr, int n)
{
	cout << sizeof(arr) << endl;    // 4
	int sum = 0;
	for (int i = 0; i < n; i++)
	{
		sum += arr[i];
        // arr[i] += 1;   // error
	}
	return sum;
}

3、const修饰符

可以使用两种不同的方式将const用于指针:

a. 第一种方法是将指针指向一个常量对象,这样可以防止使用指针来修改所指向的值。

	int n = 10;
	const int *pn = &n;
	cout << n << " " << *pn << endl;	// 10 10
	n = 20;
	cout << n << " " << *pn << endl;	// 20 20

但是,pn声明为const指针不意味着它指向的值是一个常量,也就是n是可以修改的,只是不能通过pn来修改。

C++可以将常规指针变量的地址赋给指向const的指针,可以将const变量的地址赋给指向const的指针,但是禁止将const地址赋给非const指针(除非使用const_cast)

	const int cn = 10;
	const int *pcn = &cn;
	//int *pcn2 = &cn;	// invalid

b. 第二种是将指针本身声明为常量,防止修改指针指向的位置,允许使用指针修改指向位置的值

	int n = 10, n2 = 20;
	int * const pcn = &n;
	// pcn = &n2;			// error
	*pcn = 11;
	cout << n;              // 11

4、传递二维数组

	int arr[][4]{ {1,2,3,4}, {5,6,7,8} };
	int total = total_arr(arr, 2);

如何将一个二维数组传递给函数呢?total_arr的第一个参数的类型:一个指向由4个int组成的数组的指针,即数组指针,数组指针定义如下:

int total_arr(const int(*arr)[4], int n)

另外还有一种比较容易阅读的写法: 

int total_arr(const int arr[][4], int n);

5、函数指针

如何声明函数指针:先写出函数原型,然后将函数名替换为(*pf),这样pf就是函数指针了。

double pam(int);
double(*pf)(int);

如何声明一个指向函数指针数组的指针呢?

double(*pa[3])(int);

使用typedef来定义一个函数指针

typedef const int * (*pf)(int x, int y);

如何使用:

int sum(const int *arr, int n)
{
	int sum = 0;
	for (size_t i = 0; i < n; i++)
		sum += arr[i];
	return sum;
}

typedef int(*pfunc)(const int *arr, int n);

	pfunc f = sum;
	int arr[5] = { 1, 2, 3, 4, 5};
	int ret = f(arr, 5);
	cout << ret << endl;            // 15

6、C++内联函数

内联函数运行速度比常规函数稍快,但是代价是需要占用更多的内存,每次调用内联函数都会创建函数代码的一个副本。

应该有选择地使用内联函数:如果执行函数代码的时间比处理函数调用机制的时间长,则节省的时间只占整个过程的一小部分;如果代码执行的时间很短,则内联调用就可以节省函数调用机制的时间。

内联函数不能递归,如果函数定义占用多行(如果没有使用冗长的标识符),将其作为内联函数就不太合适。

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

7、将引用用作函数参数

如何创建引用变量:

	int a = 10;
	int &b = a;
	cout << a << " " << b << endl;		// 10 10
	int c = 20;
	b = c;
	cout << a << " " << b << endl;		// 20 20
	c = 15;
	cout << a << " " << b << endl;		// 20 20
	a = 11;
	cout << a << " " << b << endl;		// 11 11

引用必须在声明时将其初始化,不能像指针那样先声明再赋值。引用更接近const指针,必须在创建时进行初始化,一旦与某个变量关联起来,将一直效忠于它。

	int * const n2 = &n;

如果我们不想让b来修改a的值,我们可以添加const修饰符:

	const int &b = a;

引用变量的主要用途是用作函数的形参:通过将引用变量用作参数,函数将使用原始数据,而不是其副本。如果只想让函数使用传递参数的信息,而不对这些信息进行修改,又想使用引用,应该是用常量引用:

    int cube(const int &n);

编写类似于上述实例的函数(基本数值类型参数),应该使用按值传递的方式,不要使用按引用传递的方式,当数据比较大时,引用参数将会很有用。

引用传递的限制会更严格,比如 cube(x + 1)是不被允许的,因为x+1不是变量。

但是如果实参和引用参数不匹配,那么C++将生产临时变量(只有引用参数是const时才会产生),只有两种情况会生成临时变量:实参的类型正确,但不是左值;实参的类型不正确,但可以转换为正确的类型,这两种情况会创建一个正确类型的临时变量,然后使用转换后的实参来初始化形参。

什么是左值?左值参数是可被引用的数据对象,例如变量、数组元素、结构成员、引用和解除引用的指针都是左值。非左值包括字面常量和包含多项的表达式。

C语言中,左值最初指的是可以出现在赋值语句左边的实体。

    int cube(const int &x)
    {
    	return x * x*x;
    }
 
	int n = 2;
	int & n1 = n;
	int *n2 = &n;
	int n3[3] = { 1, 2, 3 };
	long n4 = 2;
 
	int res = cube(n);		// x is n
	res = cube(n1);			// x is n1 is n
	res = cube(*n2);		// x is *n2 is n
	res = cube(n3[1]);		// x is n3[1]
	res = cube(n4);			// x is tmp
	res = cube(2);			// x is tmp
	res = cube(n + 1);		// x is tmp

为什么要尽可能使用const? 

a.使用const可以避免无意中修改数据的错误

b.使用const可以使函数能够处理const和非const实参,否则只能接受非const数据

c.使用const引用使函数能够正确生产并使用临时变量

将引用用于结构

引用非常适合适用于结构和类(主要是为了用于这些类型的,而不是基本的内置类型)

为什么要返回引用?传统返回机制与按值传递函数参数类似,返回引用速度会更快

struct people {
	char name[10];
	int age;
	int height;
};
people& init(people & p) {
	memcpy(p.name, "wang", 4);
	p.age = 10;
	p.height = 130;
	return p;
}

返回引用时要注意,要避免返回函数终止时不在存在的内存单元引用。避免这种问题最简单的方法,返回一个作为参数传递给函数的引用。另一种方法是使用new来分配新的内存空间,并返回指向该内存空间的指针。

const people& init2() {
	people *p = new people();
	memcpy(p->name, "wang", 4);
	p->age = 10;
	p->height = 130;
	return *p;
}

这种情况会比较容易忘了delete,后面会有unique_ptr来解决这个问题。

为何将const用于引用返回类型?常规返回类型是右值(不能通过地址访问的值),为什么常规函数返回值是右值呢?因为返回值位于临时单元中,执行下一句时,他们可能不再存在。

但是我们要使用引用返回值,但又不允许给init2()赋值这样的操作,只需要加上const,init2的返回值变成了一个不可修改的左值,赋值语句将不合法。

	people a;
	// init2() = a;	// invalid
    a = init2();    // valid

8、函数模板

函数模板是通用的函数描述,也就是说他们使用泛型来定义函数。由于类型是用参数表示的,所以模板特性有时也被称为参数化类型。

template <typename AnyType>
void mySwap(AnyType & a, AnyType & b)
{
	AnyType tmp;
	tmp = a;
	a = b;
	b = tmp;
}
 
	int a = 10;
	int b = 20;
    mySwap(a, b);
	mySwap<int>(a, b);

上述例子中调用模板函数mySwap可以指定类型,也可以不指定类型。如不指定类型,编译器或推断出类型。

不是所有的模板参数都必须是模板参数类型,我们也可以为某些参数指定固定类型:

template <typename T>
void mySwap(T & a, T & b);
 
template <typename T>
void mySwap(T * a, T * b, int n);

模板也有局限性,因为编写的模板函数很可能无法处理某些类型,解决这个问题有两种方案:一种是重载运算符,另一种解决方案是为特定类型提供具体化的模板定义。

模板具体化:显式具体化的意义是不要使用模板生成的定义,而应使用专门为job类型显示地定义地函数。

以下是两种等价的显式具体化的定义方式:

struct job
{
 
};
 
template<> 
void mySwap<job>(const job & a, const job & b)
{
	cout << "job" << endl;
}

template<> 
void mySwap(const job & a, const job & b)
{
	cout << "job" << endl;
}


job j, j1;
mySwap(j, j1);    // job

对于给定的函数名,可以有非模板函数、模板函数和显式具体化模板函数以及他们的重载版本。具体化优先于常规模板,而非模板函数优先于具体化和常规模板。

void mySwap(const job & a, const job & b)
{
	cout << "job b" << endl;
}

job j, j1;
mySwap(j, j1);        // job b

模板实例化:在代码中包含函数模板本身并不会生成函数定义,他只是一个用于生成函数定义的方案。编译器使用模板为特定类型生成函数定义时,得到的是模板实例,这种实例化方式被称为隐式实例化。以前只能通过隐式实例化来使用模板生成函数定义,现在还可以使用显示实例化。

以下是一个显式具体化的例子:

template void mySwap<job>(const job & a, const job & b);

该声明的意思是:使mySwap模板生成job类型的函数定义,生成过程中会进行模板具体化,调用时会调用该具体化函数。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

青山渺渺

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值