[C.C++] C++函数

627 0
Honkers 2026-1-9 23:37:58 来自手机 | 显示全部楼层 |阅读模式

先记录一些零碎的内容

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

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

  1. #define BYTE char
  2. #define BYTE_POINTER char*
  3. BYTE_POINTER p1, p2;
  4. // char *p1, p2;
复制代码

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

  1. typedef typeName aliasName;
  2. typedef char BYTE;
  3. typedef char * BYTE_POINTER;
复制代码

1、函数参数与按值传递

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

2、给函数传递一维数组

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

  1. int sum_arr(int arr[], int n)
  2. {
  3. cout << sizeof(arr) << endl; // 4
  4. int sum = 0;
  5. for (int i = 0; i < n; i++)
  6. {
  7. sum += arr[i];
  8. arr[i] += 1;
  9. }
  10. return sum;
  11. }
  12. int arr[]{ 1,2,3,4,5 };
  13. int sum = sum_arr(arr, sizeof(arr));
  14. for (size_t i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
  15. cout << arr[i] << " "; // 2 3 4 5 6
复制代码

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

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

  1. int sum_arr2(const int *arr, int n)
  2. {
  3. cout << sizeof(arr) << endl; // 4
  4. int sum = 0;
  5. for (int i = 0; i < n; i++)
  6. {
  7. sum += arr[i];
  8. // arr[i] += 1; // error
  9. }
  10. return sum;
  11. }
复制代码

3、const修饰符

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

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

  1. int n = 10;
  2. const int *pn = &n;
  3. cout << n << " " << *pn << endl; // 10 10
  4. n = 20;
  5. cout << n << " " << *pn << endl; // 20 20
复制代码

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

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

  1. const int cn = 10;
  2. const int *pcn = &cn;
  3. //int *pcn2 = &cn; // invalid
复制代码

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

  1. int n = 10, n2 = 20;
  2. int * const pcn = &n;
  3. // pcn = &n2; // error
  4. *pcn = 11;
  5. cout << n; // 11
复制代码

4、传递二维数组

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

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

  1. int total_arr(const int(*arr)[4], int n)
复制代码

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

  1. int total_arr(const int arr[][4], int n);
复制代码

5、函数指针

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

  1. double pam(int);
  2. double(*pf)(int);
复制代码

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

  1. double(*pa[3])(int);
复制代码

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

  1. typedef const int * (*pf)(int x, int y);
复制代码

如何使用:

  1. int sum(const int *arr, int n)
  2. {
  3. int sum = 0;
  4. for (size_t i = 0; i < n; i++)
  5. sum += arr[i];
  6. return sum;
  7. }
  8. typedef int(*pfunc)(const int *arr, int n);
  9. pfunc f = sum;
  10. int arr[5] = { 1, 2, 3, 4, 5};
  11. int ret = f(arr, 5);
  12. cout << ret << endl; // 15
复制代码

6、C++内联函数

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

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

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

  1. inline int square(int x) { return x * x; }
复制代码

7、将引用用作函数参数

如何创建引用变量:

  1. int a = 10;
  2. int &b = a;
  3. cout << a << " " << b << endl; // 10 10
  4. int c = 20;
  5. b = c;
  6. cout << a << " " << b << endl; // 20 20
  7. c = 15;
  8. cout << a << " " << b << endl; // 20 20
  9. a = 11;
  10. cout << a << " " << b << endl; // 11 11
复制代码

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

  1. int * const n2 = &n;
复制代码

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

  1. const int &b = a;
复制代码

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

  1. int cube(const int &n);
复制代码

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

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

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

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

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

  1. int cube(const int &x)
  2. {
  3. return x * x*x;
  4. }
  5. int n = 2;
  6. int & n1 = n;
  7. int *n2 = &n;
  8. int n3[3] = { 1, 2, 3 };
  9. long n4 = 2;
  10. int res = cube(n); // x is n
  11. res = cube(n1); // x is n1 is n
  12. res = cube(*n2); // x is *n2 is n
  13. res = cube(n3[1]); // x is n3[1]
  14. res = cube(n4); // x is tmp
  15. res = cube(2); // x is tmp
  16. res = cube(n + 1); // x is tmp
复制代码

为什么要尽可能使用const? 

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

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

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

将引用用于结构

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

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

  1. struct people {
  2. char name[10];
  3. int age;
  4. int height;
  5. };
  6. people& init(people & p) {
  7. memcpy(p.name, "wang", 4);
  8. p.age = 10;
  9. p.height = 130;
  10. return p;
  11. }
复制代码

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

  1. const people& init2() {
  2. people *p = new people();
  3. memcpy(p->name, "wang", 4);
  4. p->age = 10;
  5. p->height = 130;
  6. return *p;
  7. }
复制代码

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

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

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

  1. people a;
  2. // init2() = a; // invalid
  3. a = init2(); // valid
复制代码

8、函数模板

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

  1. template <typename AnyType>
  2. void mySwap(AnyType & a, AnyType & b)
  3. {
  4. AnyType tmp;
  5. tmp = a;
  6. a = b;
  7. b = tmp;
  8. }
  9. int a = 10;
  10. int b = 20;
  11. mySwap(a, b);
  12. mySwap<int>(a, b);
复制代码

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

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

  1. template <typename T>
  2. void mySwap(T & a, T & b);
  3. template <typename T>
  4. void mySwap(T * a, T * b, int n);
复制代码

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

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

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

  1. struct job
  2. {
  3. };
  4. template<>
  5. void mySwap<job>(const job & a, const job & b)
  6. {
  7. cout << "job" << endl;
  8. }
  9. template<>
  10. void mySwap(const job & a, const job & b)
  11. {
  12. cout << "job" << endl;
  13. }
  14. job j, j1;
  15. mySwap(j, j1); // job
复制代码

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

  1. void mySwap(const job & a, const job & b)
  2. {
  3. cout << "job b" << endl;
  4. }
  5. job j, j1;
  6. mySwap(j, j1); // job b
复制代码

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

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

  1. template void mySwap<job>(const job & a, const job & b);
复制代码

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

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行