摘要:OOP(面向对象),namespace,cout and cin,缺省参数,函数重载,引用,内联函数,auto,范围 for,nullptr
20世纪80年代,计算机界提出了OOP(object oriented programming:面向对象)思想,支持面向对象的程序设计语言应运而生。C++是基于C语言而 产生的,它既可以进行C语言的过程化程序设计,又可以进行以抽象数据类型为特点的基于对象的程序设计,还可以进行面向对象的程序设计。
A major design goal of C++ is to let programmers define their own types that are as easy to use as the built-in types.
——《C++ Primer》
namespace fantasy
{
int a = 13;
int l = 7;
struct MyStructRB
{
int capacity;
int size;
}RB;
}
int main()
{
int sum = fantasy::a + fantasy::l;
fantasy::RB.size = 0;
fantasy::RB.capacity = 0;
return 0;
}
Question:全局变量与局部变量冲突时,优先哪个?
#include
int num = 7;
int main()
{
int num = 13;
printf("%d",num);
return 0;
}
Answer:局部变量,因为查找的顺序是:先局部 ⇢ 再全局(output: 13)
name_of_namespace ,意为在该命名空间的区域内查找变量,如果左操作数为空,则表示在全局查找。
namespace fantasy
{
int a = 13;
int l = 7;
namespace RoundBottle
{
struct MyStructRB
{
int* array;
int size;
}RB;
}
}
int main()
{
fantasy::RoundBottle::RB.size = 0;
fantasy::RoundBottle::RB.array = nullptr;
return 0;
}
std::cout << "Hello";
using namespace name_of_namespace;
using namespace std;
#include
using namespace std;
int main()
{
int num = 0;
// 可以自动识别变量的类型
cin >> num;//把数据输入到 num 这个变量中进行储存
cout << num << endl;//将num中储存的数据输出(到屏幕)
return 0;
}
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参。
//全缺省
int Add(int x = 1, int y = 2)
{
return x + y;
}
//部分缺省
int Sum(int x, int y, int z = 3)
{
return x + y + z;
}
#include
using namespace std;
int Sum(int x = 1, int y, int z = 3)
{
return x + y + z;
}
int main()
{
int ret = Sum(1, 2);
cout << ret << endl;
return 0;
}
上述情况下,参数将无法确定传给谁,例如,参数“1”可能传给 x,也可能传给 y,因此这里出现了歧义,导致编译错误。同样的,上述代码也不支持写成: ret = Sum( , 1, 2);
//函数声明:
int Add(int x = 1, int y = 2);
//函数定义:
int Add(int x = 1, int y = 2)
{
return x + y;
}
(举例:栈的初始化)
C:
// 初始化栈
void StackInit(Stack* ps)
{
STDataType* tmp = (STDataType*)malloc(4 * sizeof(STDataType));
if (!tmp)
{
perror("malloc fail");
exit(-1);
}
ps->_a = tmp;
ps->_top = 0;
ps->_capacity = 4;
}
CPP:
// 初始化栈
void StackInit(Stack* ps, int initsize = 4)
{
int* tmp = (int*)malloc(initsize * sizeof(int));
if (!tmp)
{
perror("malloc fail");
exit(-1);
}
ps->_a = tmp;
ps->_top = 0;
ps->_capacity = initsize;
}
知道栈中最多存100个数据:
StackInit(&stack, 100);
不知道栈中最多存多少数据:
StackInit(&stack);
函数重载 → 在同一个命名空间,且函数名相同,参数不同,体现为三种方式:
Answer:❌不能!函数调用的时候并不能指明返回值,当两个函数只有返回值的类型不同时,在尝试调用函数时无法确认将要调用哪个函数,将发生编译错误!返回值类型是否相同与函数构成重载不相关。
首先,我们需要了解代码的编译过程:(这里简单描述一下)
在链接的环节会生成符号表,类似这样:
name | address |
---|---|
function | xxxxxxxxxx |
main | xxxxxxxxxx |
…… | …… |
链接的时候会通过 name 找到 address,函数地址是一个跳转指令,如果函数没有定义只有声明就不会有地址,这将发生链接错误。
sum.
#include
using namespace std;
int main()
{
int v1 = 13;
int& v2 = v1;
v2 = 7;
cout << v1 << endl; //output:7
return 0;
}
定义时必须初始化
一个变量可以有多个引用
int main()
{
int v1 = 13;
int& v2 = v1;
int& v3 = v1;
int& v4 = v1;
int* p = &a;
int*& pb = p;
return 0;
}
引用在定义的时候必须初始化,因此只能引用一个实体,并且不能改变,否则会出现重定义
int main()
{
int v1 = 13;
int v2 = 7;
int& v3 = v1;
v3 = v2;//这是赋值,将v2的数据赋值给v3,同样的也是指v1
int& v3 = v2;//错误:重定义!!
return 0;
}
int main()
{
const int v = 13;
int& v1 = 13;//error
const int& v2 = 13;
const int& v3 = v2;//correct
return 0;
}
上述代码中,“13” 的 Type 是 const int,是只读不可修改的常量,在语句 int& v1 = 13 中本是上发生了权限放大,导致错误。v1 的是 Type 是 int,是可读可修改的变量。权限可以平移、缩小,但不能放大。
隐式类型转化_Type Conversion
int main()
{
int v_i = 0;
double& v_d = v_i;//error
double v_d2 = v_i;//correct,同样发生了类型转化,但这只是赋值语句
return 0;
}
上述代码发生了隐式类型转化,int → double ,转化过程中会产生临时变量,临时变量具有常属性!本质上来说这里仍发生了权限放大。
以值作为参数或者返回值类型,在传参和返回期间,函数不会直接传递实参或者将变量本身直接返回,而是传递实参或者返回变量的一份临时的拷贝,因此用值作为参数或者返回值类型,效率是非常低下的,尤其是当参数或者返回值类型非常大时,效率就更低。
传引用的作用:①减少拷贝;②调用者可以修改该对象
传引用传参:输出型函数,形参的改变需要影响实参
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int x = 13, y = 7;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}
传引用返回:函数的返回类型为一个引用
方式一:上图中的传引用返回可以直观的理解为下图中的代码 ⬇ ,_num 与 num 是同一块存储空间的不同名称,然后将这个空间中存储的数据赋值给变量 v 。warning:这样的行为是未定义的,因为 num 在出函数作用域时候被销毁,该存储空间被归还,再次访问这块空间会得到什么数据是不确定的。
方式二:“用引用来接收引用返回”,以下代码中,v 与 num 是同一块存储空间的不同名称,即表示同一块存储空间。
int& Count()
{
int num = 0;
++num;
return num;
}
int main()
{
int& v = Count();
cout << v << endl;//ouput:1
cout << v << endl;//ouput:随机值
return 0;
}
语法上有区别,但底层实现本质上是相同的。
当我们尝试实现一个可以进行加法运算的宏函数:以下 3 种写法都容易在使用 ADD宏函数的时候出现问题。
#define ADD(x,y) x+y
#define ADD(x,y) (x+y)
#define ADD(x,y) ((x)+(y));
//better
#define ADD(x,y) ((x)+(y))
宏函数的优点:
宏函数的缺点:
基于此,C++选择使用内联函数:以 inline 修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升了程序运行的效率。
为了在调试的时候看到内联函数的展开我们需要关闭优化:步骤如下列图片
补充:关于代码膨胀
对于内联函数,如果该函数定义包含 100 行代码,调用该函数 100 次,如果被内联展开,最终会被编译成 100*100 行指令,导致代码膨胀。对于非内联函数,如果该函数定义包含 100 行代码,调用该函数 100 次,最终会被编译成 100+100 行指令。因此,体量过大的、调用频繁的函数不建议内联展开。
功能:自动识别类型
int main()
{
int i = 0;
auto v = i;
auto v2 = 0;
return 0;
}
补充:查看变量的类型 —— typeid
int main()
{
auto v = 1;
const type_info& vInfo = typeid(v);
cout << vInfo.name() << endl;//output:int
return 0;
}
int main()
{
int array[] = { 1, 2, 3, 4, 5 };
cout << array << endl;
for (auto& var : array)
{
var *= 2;
cout << &var << " " << var << endl;
}
for (auto num : array)
cout << num << " ";
//ouput:2 4 6 8 10
//这里的var和num都是变量名,两者一不一样或取什么名字都是不影响的
return 0;
}
注意:
void func(int array[])
{
for (auto e : array)//error
{
……
cout << e << endl;
}
}
int main()
{
int array[] = { 1,2,3,4 };
for (auto e : array[0])//also error
{
……
}
return 0;
}
简而言之,NULL在C++中出现了错误,所以引入 nullptr
#define NULL 0 //error
#define NULL ((void*)0)
nullptr 使用注意:
- 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入 的。
- 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。
- 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。
END