C嘎嘎~~【初识C++ 中篇】

简介: C嘎嘎~~【初识C++ 中篇】

1.缺省参数

缺省参数是 声明或定义函数时 为 函数的参数指定一个缺省值。在调用该函数时, 如果没有指定实参则采用该形参的缺省值, 否则使用指定的实参


void func(int a = 5)
{
  cout << a << endl;
}
int main()
{
  func(); // 5
  func(10); // 10
  return 0;
}

关于缺省的一些分类, 做了下面的总结:

c9076b1c7b774cb2b6ecb0d8dece206d.png


有一些老铁, 就会问:为啥半缺省的时候要从左至右传参, 从右至左缺省啊??


首先, 我们要明确一点,缺省是从右至左的, 那我们如果传参的时候也按照同样的顺序进行传参, 如果传参的个数和缺省的个数是一样的, 那么缺省的意义是什么呢?


下面的代码是缺省函数的一个应用:


typedef struct Stack
{
  int* a;
  int top;
  int size;
  int capacity;
}ST;
void STInit(ST* head, int DEFAULT = 10) 
{
  int* tem = (int*)malloc(sizeof(int*) * DEFAULT);
  if (tem == NULL)
  {
    perror("malloc fail");
    return;
  }
  head->a = tem;
  head->capacity = DEFAULT;
}
int main()
{
  ST st;
  STInit(&st); // 使用10
  STInit(&st, 20); // 使用20
  return 0;
}

在单链表、 栈 、 队列这些线性结构中, 初始化要给多少空间是拿捏不准的,如果给大了, 用户用的少; 如果给小,用户不够用,还要增容(增容是很浪费空间 和 时间的)。所以, 我们想能不能有这样的情况: 先给一个默认值, 如果用户感觉小了, 再给大一点; 如果感觉适中, 就用这个默认值。

刚学的缺省参数就派上了用场, 这样多方便啊。 在C语言中, 可以用 # define MIN 10; (宏)来替代一些作用, 但是他不能像缺省参数这样可以随意改变大小啊?



注意:


1.缺省参数必须从右至左依次来给, 不能间隔着给

2.缺省值一般是常量、全局变量(这个一般不推荐)

3.如果声明和定义分明(声明在.h 文件中, 定义在.cpp 文件中),缺省参数不能在函数定义 和 声明中同时给的(缺省参数是给声明的, 因为在函数的编译环节, 编译器看到的是函数声明,在最后的链接过程,才找到定义);如果函数的声明和定义是在同一个文件中, 可以给声明,也可以给定义(因为在这个过程中, 函数的编译 和 链接过程是一起进行的)


2.函数重载

中国文化博大精深, 历史源远流长。在我们的日常生活中, 有许多词语一词多义, 单拎出来,不知道是什么意思,要借助上下文来判断该词语的真正含义, 即改词被重载了。


2.1函数重载的概念

函数重载: 是C++中函数的一种特殊情况, 允许在同一作用域中声明几个功能类似的同名函数, 这些同名函数的形参列表(参数个数、 类型 或 类型顺序)不同, 常用来处理实现功能类似数据类型不同的问题


1.参数个数不同

int Sub(int a, int b)
{
  cout << "Sub(int a, int b)" << endl;
  return a - b;
}
int Sub(int a)
{
  cout << "Sub(int a)" << endl;
  return a;
}
int main()
{
  Sub(5); // Sub(int a)
  Sub(10, 8); // Sub(int a, int b)
  return 0;
}

2.类型顺序不同

int Sub(int a, int b)
{
  cout << "Sub(int a, int b)" << endl;
  return a - b;
}
int Sub(int a, double b)
{
  cout << "Sub(int a, double b)" << endl;
  return a;
}
int main()
{
  Sub(5, 1.5); // Sub(int a, double b)
  Sub(10, 8); // Sub(int a, int b)
  return 0;
}

3.类型不同

int Sub(double a, int b)
{
  cout << "Sub(double a, int b)" << endl;
  return a - b;
}
int Sub(int a, double b)
{
  cout << "Sub(int a, double b)" << endl;
  return a;
}
int main()
{
  Sub(5, 1.5); // Sub(int a, double b)
  Sub(1.5, 8); // Sub(double a, int b)
  return 0;
}

有些老铁看到这里, 忍不住问了: 那我如果函数返回类型不同, 但函数的形参列表相同,构不构成函数重载??


首先, 上面所说的构成函数重载的条件是形参列表不同, 即形参的类型、 形参的个数 或 形参类型的顺序不同;

其次, 如果想看原理性的答案, 请移步下面 “函数重载的原理”


2.2函数重载的原理


a4d6814004af41d1a370cd6e7b6e36e1.png


C++的函数重载,依赖于C++对函数进行了修饰(不同的平台,修饰规则不同),然后才会自动匹配类型/ 自动识别类型。

通过下面的一张图, 使我们对C 和 C++ 的函数调用有个更深刻的理解


0d7c4cce38a6478fa7a107ba26867600.png


来一个面试题来检查一下我们上面的两个内容:


void Add(int a = 0)
{
  cout << "Add(int a = 0)" << endl;
}
void Add()
{
  cout << "Add()" << endl;
}

上面的代码能不能构成重载?

首先, 能构成重载(因为参数的个数不同)

其次, 无参调用时,会存在歧义, 重载不明确


3.auto关键字

3.1类型别名思考(typedef)

随着程序越来越复杂, 程序中用到的类型也越来越复杂, 经常体现在:


1.类型难于拼写

2.含义不明确导致容易出错


以后我们要学的迭代器很长、也很容易写错, 我们用atuo(自动匹配类型)就很舒服。 有些聪明的老铁就会反问:我们用 typedef 也不是一样的效果嘛? 使用 typedef 给类型取别名 确实是可以简化代码, 但是也有些新的问题,见下面的代码:

typedef int* pint;
int main()
{
  const pint p1; // 能否编译成功
  const pint* p2; // 能否编译成功
  return 0;
}

在编程时, 通常要给变量 或 表达式 赋初值, 这就要求在声明变量的时候要清楚地知道表达式的类型, 然而有时候要做到这一点并非是很容易的, 因此C++给 auto 赋予了新的含义。


3.2auto的简介

C++11中, 标准委员会赋予了 auto 全新的含义: auto 不再是 一个存储类型指示符, 而是作为一个全新的类型指示符来指示编译器, auto 声明的变量必须有编译器在编译时期推到而得。简而言之, 在新的C++标准中, 使用 auto 可以由推导变量 或者是表达式的类型。

通过下面的代码, 我们来清楚地看一下:


double Add(int a, double b)
{
  return a + b;
}
int main()
{
  int a;
  auto b = 2.1;
  auto c = 'a';
  cout << typeid(a).name() << endl; // int
  cout << typeid(b).name() << endl; // double
  cout << typeid(c).name() << endl; // char
  cout << typeid(Add).name() << endl; // double __cdecl(int,double)
  return 0;
}

总结:


1.使用 auto 定义变量的时候必须要对其进行初识化, 在编译阶段, 编译器需要根据初始化来推导 auto 的实际类型

2.auto 并非是一种 “类型” 的声明, 而是一个类型声明时的 “占位符”, 编译器在编译时会将 auto 这个东东 替换成 变量的实际类型


3.3auto的使用规则

1.auto 与指针 和 引用结合起来使用

先让我们来看一下代码:


int main()
{
  int x = 10;
  auto a = &x; // 推导a是指针
  auto* b = &x; // 指定b是指针, 如果右边不是指针就会报错
  auto& c = x; // 指定c是引用
  cout << typeid(a).name() << endl; // int * __ptr64
  cout << typeid(b).name() << endl; // int * __ptr64
  cout << typeid(c).name() << endl; // int
  *a = 20;
  *b = 40;
  c = 50;
  cout << a << " " << *a << endl; // 00000030CB8FFA44 50
  cout << b << " " << *b << endl; // 00000030CB8FFA44 50
  cout << &c << " " << c << endl; // 00000030CB8FFA44 50
  return 0;
}

如上图结果所知: 用 auto 声明指针类型时, auto 和 auto* 是一样的,没有任何区别。但是 auto 声明引用类型时, 必须要加&。


2.在同一行定义多个变量

int main()
{
  auto a = 1, b = 2; // 能够编译成功
  auto c = 1, d = 4.0; // 编译失败, c 和 d的初始化的类型是不一样的
  return 0;
}

用 auto 在同一行定义多个变量时, 这些变量必须是相同的类型, 只要有变量的类型有不一样的,就会编译错误。

因为编译器实际上只对第一个类型进行推导(从左至右),然后用推导出来的类型来定义其他变量。

3.4 auto不能推导的场景

1.auto 不能作为函数的参数(肯定是形参啊)

int Sub(auto a); // 会编译错误, auto 不能作为形参参数, 因为编译器不能对 a 的实际类型进行推导
int main()
{
  int a = 0;
  Sub(a);
  return 0;
}

2.auto 不能直接用来声明数组

int main()
{
  int a[] = { 1, 2, 3 };
  auto b[] = { 4, 5, 6 }; // 即使有[],编译器也不能进行推导
  return 0;
}

3.auto 在实际中最常用的优势用法是C++ 提供的新式for 循环, 还有lambda表达式等进行配合使用


4.基于范围的for循环

这个数组的范围是确定的啊, 为啥会报越界的error啊??

不理解、不理解!!

祖师爷,来出一个东东来解决一下吧

星光荡开宇宙, 范围for循环闪耀登场


4.1范围for的语法

对于一个有范围的集合而言, 对程序员来说循环的范围是多余的, 有时候还容易犯错误!!因此, 我们的祖师爷就引入了基于范围的for循环。

基本构成: 原本for循环()内的内容变成 由冒号(:)分成的两部分; (类型 : 有范围的集合)


让我们通过下面的代码来清楚地看一下:


int main()
{
  int arr[] = { 1,2,3,4,5 };
  for (int a : arr)
  {
    cout << a << " ";
  }
  cout << endl; // 1 2 3 4 5
  for (auto a : arr)
  {
    cout << a << " ";
  }
  cout << endl; // 1 2 3 4 5
  for (auto& a : arr)
  {
    a *= 2; // 将每个数都乘2
    cout << a << " ";
  }
  cout << endl; // 2 4 6 8 10
  return 0;
}

总结:


1.auto 和 引用(&)跟 范围for循环 配合使用是非常的香

2.与普通循环类似, 可以用 continue 来结束本次循环, 也可以用 break 来跳出整个循环


4.2范围for的使用条件

1.for循环迭代的范围必须是确定的

对于数组而言, 就是数组中第一个元素 和 最后一个元素 的范围

对于类而言, 应该提供begin 和 end的范围, begin 和 end 就是循环迭代的范围

void test(int arr[]) // 这个就是错误的, 因为这里arr是一个指针, 导致循环的范围是不确定的
{
  for (auto a : arr)
  {
    cout << a << end;
  }
}

2.迭代的对象要实现 ++ 和 == 的操作(关于迭代器的问题, 这个以后再讲)


行路难, 行路难, 多歧路, 今安在?

长风破浪会有时, 直挂云帆济沧海。


相关文章
|
2月前
|
编译器 C++
C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)
C++进阶之路:何为运算符重载、赋值运算符重载与前后置++重载(类与对象_中篇)
33 1
|
2月前
|
存储 编译器 C++
C++进阶之路:何为拷贝构造函数,深入理解浅拷贝与深拷贝(类与对象_中篇)
C++进阶之路:何为拷贝构造函数,深入理解浅拷贝与深拷贝(类与对象_中篇)
25 0
|
3月前
|
编译器 C语言 C++
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题(下)
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题
40 2
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题(下)
|
3月前
|
算法 编译器 C语言
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题(上)
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题
33 3
|
3月前
|
编译器 C语言 C++
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题(中)
从C语言到C++⑥(第二章_类和对象_中篇_续)大练习(日期类)+笔试选择题
34 1
|
3月前
|
编译器 C语言 C++
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)(下)
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)
16 1
|
2月前
|
编译器 C++
C++进阶之路:何为默认构造函数与析构函数(类与对象_中篇)
C++进阶之路:何为默认构造函数与析构函数(类与对象_中篇)
22 0
|
3月前
|
编译器 C语言 C++
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)(中)
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)
18 0
|
3月前
|
编译器 C语言 C++
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)(上)
从C语言到C++⑤(第二章_类和对象_中篇)(6个默认成员函数+运算符重载+const成员)
16 0
|
3月前
|
存储 安全 编译器
从C语言到C++②(第一章_C++入门_中篇)缺省参数+函数重载+引用(下)
从C语言到C++②(第一章_C++入门_中篇)缺省参数+函数重载+引用
31 0