目录
[(2)或 参数类型不同](#(2)或 参数类型不同)
[(2)或 参数个数不同](#(2)或 参数个数不同)
②一个引用对应一个引用对象,且不能更改引用对象(和指针有区别)。
①vs编译器debug版本默认不展开inline(方便调试)。
(一)第一个程序
cpp
#include<iostream>
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
(二)命名空间
(1)C中遇到的问题
头文件中定义的变量和我们自己定义的变量都是全局变量,存在冲突。
cpp
#include<stdio.h>
#include<stdlib.h>
int rand = 10;
int main()
{
// error:rand redefinition
printf("%d\n",rand);
return 0;
}
(2)引入namespace
我们在全局定义一个自己的namespace:
cpp
namespace wyzy
{
int rand = 10;
int Add(int num1,int num2)
{
return num1 + num2;
}
struct Node
{
int a = 1;
struct Node* next;
};
}
如果我们需要输出我们自己的rand:
cpp
int main()
{
printf("%d\n",wyzy::rand);
return 0;
}
- namespace本质是定义一个域和全局域隔离。
- 局部域、全局域、命名空间域、类域都影响编译时语法查找变量的逻辑;局部域和全局域除此之外还会影响变量的生命周期。
- namespace只能定义在全局,可以嵌套定义。
- 项目工程中各文件中同名namesapce会认为是同一个namesapce。
- C++标准库的namespace叫std(standard)。
(3)命名空间的使用
①指定命名空间访问
cpp
int main()
{
std::cout << wyzy::rand << std::endl;
return 0;
}
②using展开namespace中某个成员
cpp
using wyzy::rand;
int main()
{
std::cout << rand << std::endl;
return 0;
}
③展开命名空间中全部成员
cpp
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
(三)C++输入输出
- 头文件iostream是input output stream的缩写。
- std::cin是istream的对象,面向窄字符(还没学到)的标准输入流。
- std::cout是ostream的对象,面向窄字符(还没学到)的标准输出流。
- std::endl是一个函数,相当于输出一个换行字符+刷新缓冲区(具体还没学)。
- <<:流插入运算符;>>:流提取运算符。(这两个符号也是位运算符号)
- C++的输入输出可以自动识别变量类型,能够更好支持自定义类型对象的输入输出。
- vs系列编译器中,<iostream>中包含了<stdio.h>。
(四)缺省参数
若函数调用时没有给实参,则函数会有一个缺省参数作为默认参数。
(1)全缺省
cpp
int Add(int a = 10, int b = 10)
{
return a + b;
}
int main()
{
cout << Add() << endl;
return 0;
}
这里调用Add()函数,但是a,b的值都没有给出,
因此函数采用缺省值10,10。
最后输出计算结果20。
(2)半缺省
cpp
int Add(int a = 10, int b = 10)
{
return a + b;
}
int main()
{
cout << Add(1) << endl;
return 0;
}
这里调用Add()函数,只给出了a值为1,
因此函数对b值采用缺省值10。
最后输出计算结果11。
(3)报错情况
①形参给值&实参给值
cpp
int Add(int a = 10,int b) // !!报错
{
return a + b;
}
int main()
{
cout << Add(1) << endl; // !!报错
return 0;
}
这里要注意:
- 形参要从左到右给缺省值。
- 调用函数的时候,要从左到右给实参。
大家可以自己试一试,怎么样会报错,怎么样不会报错。
②函数声明&定义分离
头文件defaultParameter.h:
cpp
int Add(int a,int b);
实现函数文件:
cpp
#include "defaultParameter.h"
int Add(int a = 10,int b = 10)
{
return a + b;
}
测试文件:
cpp
int main()
{
cout << Add() << endl; // 报错
return 0;
}
这里必须在头文件里,函数声明的时候给缺省值,函数定义的时候可以省略缺省值。
(五)函数重载
触发函数重载的条件:
(1)同一作用域同名函数
(2)或 参数类型不同
cpp
int Add(int a,int b)
{
return a + b;
}
double Add(double a,double b)
{
return a + b;
}
这里我们定义两个同名函数,一个对整数相加,一个对浮点数相加。
cpp
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.2, 2.11) << endl;
return 0;
}
-
当我们实参给整数时,调用Add(int a,int b)。
-
当我们实参给浮点数时,调用Add(double a,double b)。
(2)或 参数个数不同
cpp
void print()
{
cout << "1" << endl;
}
void print(char c)
{
cout << c << endl;
}
这里我们定义两个同名函数,一个没有参数,一个只有一个参数。
cpp
int main()
{
print();
print('2');
return 0;
}
-
当我们不给实参时,调用print()。
-
当我们给一个实参时,调用print(char c)。
(六)引用
引用即给已存在变量取别名。
(1)先给一段使用引用的代码
cpp
int main()
{
int a = 1;
int& aa = a;
return 0;
}
(2)两者的地址是相同的
cpp
int main()
{
int a = 1;
int& aa = a;
cout << &a << endl;
cout << &aa << endl;
return 0;
}
(3)区分类似的符号
- &:前置取地址,后置引用。
- *:前置解引用,后置指针。
(4)注意点
①定义引用的时候必须初始化。
cpp
int main()
{
int& a; // 报错
return 0;
}
②一个引用对应一个引用对象,且不能更改引用对象(和指针有区别)。
Ⅰ.指针更改指向对象
cpp
int main()
{
int a = 1;
int b = 2;
int* pa = &a;
int* pb = &b;
pa = pb;
cout << "pa:" << pa << endl;
cout << "pb:" << pb << endl;
return 0;
}
这里pa = pb,pa最后指向了b这块空间。
Ⅱ.引用不能更改引用对象
cpp
int main()
{
int a = 1;
int b = 2;
int& aa = a;
int& bb = b;
aa = bb;
cout << "&aa:" << &aa << endl;
cout << "&bb:" << &bb << endl;
return 0;
}
这里aa = bb只是把bb的值赋给aa了。
(5)引用的使用
①引用传参
原先我们写Swap函数:
cpp
void Swap(int* pn1,int* pn2)
{
int tmp = *pn1;
*pn1 = *pn2;
*pn2 = tmp;
}
现在可以用引用传参:
cpp
void Swap(int& num1,int& num2)
{
int tmp = num1;
num1 = num2;
num2 = tmp;
}
主函数输出:
cpp
int main()
{
int n1 = 1;
int n2 = 2;
Swap(n1, n2);
cout << n1 << endl;
cout << n2 << endl;
return 0;
}
最后输出的n1,n2的值符合预期。
②引用做返回值
顺便回顾我们在数据结构中,对于栈的实现。
cpp
typedef int STDataType;
typedef struct Stack
{
STDataType* arr;
int capacity;
int top;
}ST;
void STInit(ST& s,int n = 4)
{
assert(&s);
s.arr = (STDataType*)malloc(n * sizeof(STDataType));
s.capacity = n;
s.top = 0;
}
void STPush(ST& s, STDataType x)
{
assert(&s && s.arr && s.top >= 0);
if (s.capacity == s.top)
{
//...
}
s.arr[s.top++] = x;
}
int& STTop(ST& s)
{
assert(&s && s.arr && s.top > 0);
return s.arr[s.top-1];
}
void destroy(ST& s)
{
assert(&s && s.arr);
free(s.arr);
s.arr = NULL;
s.capacity = 0;
s.top = 0;
}
int main()
{
Stack s;
STInit(s);
STPush(s, 1);
STPush(s, 2);
cout << STTop(s) << endl;
++STTop(s);
cout << STTop(s) << endl;
destroy(s);
return 0;
}
这里STTop的返回值为int&,我们可以直接改变这个返回值,以达到改变栈顶元素值的目的。
(6)const引用
①使用注意点
cpp
int main()
{
const int a = 1;
int& aa = a; // 报错
return 0;
}
访问权限在引用过程中只能缩小或不变,不能放大。
不过这里涉及临时对象的问题。
②临时对象
临时对象相当于已经被const修饰了,如果要定义它的引用,只能用const引用。
Ⅰ.
cpp
int a = 1;
int& aa = a * 2; // 报错
cpp
int a = 1;
const int& aa = a * 2; // 不报错
在这里 a*2 的结果保存在临时对象中。
Ⅱ.
cpp
double d = 12.3;
int& dd = d; // 报错
cpp
double d = 12.3;
const int& dd = d; // 不报错
在这里12.3类型转换的结果保存在临时对象中。
Ⅲ.
cpp
int Add(int a,int b)
{
return a + b;
}
int main()
{
int& aa = Add(1,1); // 报错
return 0;
}
cpp
int main()
{
const int& aa = Add(1,1); // 不报错
return 0;
}
这里a + b的结果保存在临时对象中。
(7)指针和引用的区分
- 在语法上,指针需要开辟新空间,引用和引用对象共用一块空间(具体还没学到)。
- 在语法上,指针定义的时候可以不初始化(建议初始化),引用必须初始化。
- 指针可以改变指向对象,引用不能改变引用对象。
- 指针需要解引用才能真正访问指向对象,引用可以直接访问引用对象。
- 指针容易出现空指针和野指针的问题,引用很少出现这些问题。
(七)inline
inline是针对宏函数设计出来的语法。
(1)首先回顾一下宏函数
cpp
#define Add(a,b)((a)+(b))
cpp
int main()
{
int add = Add(1,2);
return 0;
}
我们调用宏函数的时候,这个函数会原地展开,替换成((1)+(2))。
(2)使用inline
cpp
inline int Add(int x,int y)
{
return x + y;
}
int main()
{
int add = Add(1, 2);
cout << add << endl;
return 0;
}
我们通过汇编观察程序是否展开:
①vs编译器debug版本默认不展开inline(方便调试)。
②我们需要调一下设置,按照如图箭头所示。


③开始调试,转到反汇编

可以看到call指令被展开了

(3)注意点
- 代码稍微多一点的inline函数不会被展开,具体取决于编译器(大家可以自行尝试多少代码量不会被展开)。
- inline函数不建议声明和定义分离到两个文件,会报错。

(八)nullptr
- 在C中NULL被定义为无类型指针(void*)的常量,在C++中常被定义为整数常量0。
- 当我们需要用到空指针(可以转换成任意其它类型的指针),就可以用到关键字"nullptr"(C++11引入)
使用场景
有两个同名函数:
cpp
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
想要使用NULL调用f(int* ptr),结果却调用到了f(int x):
cpp
int main()
{
f(0); // 输出:f(int x)
f(NULL); // 输出:f(int x)
}
如果我需要用NULL调用f(int* ptr),得给NULL类型转换:
cpp
f((int*)NULL); // 输出:f(int* ptr)
我想省事一些,因此选择nullptr
cpp
f(nullptr); // 输出:f(int* ptr)