前言:
C++相对于C语言还有很多区别,接下来我们继续介绍
函数重载:
很好理解,就是Java中的函数重载。C++加了函数的修饰,通过函数修饰规则去找。C语言是直接通过函数名查找,C++是通过修饰后的函数名去查找。
引用:
注意,这个知识点很重要。引用不是新定义一个变量,而是给已经存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量公用同一块内存空间。
相当于给其他人取了别名。
int main()
{
int a = 0;
//引用:b是a的别名
int& b = a;
//地址都是一样的
cout << &a << endl;
cout << &b << endl;
return 0;
}
可以发现, 地址都是一样的。
我们可以不用传地址,通过引用(取别名)的方式交换实参的值。
void Swap(int& a, int& b)
{//a是x的别名 b是y的别名
//所以可以完成交换
int tmp = a;
a = b;
b = tmp;
}
int main()
{
int x = 1, y = 0;
Swap(x, y);
cout << x << endl;
cout << y << endl;
return 0;
}
这里可以看出,引用有点类似于指针;引用确实可以改变实参,其实引用本身就是为了方便我们的学习,但是我们如果想要学得更好,还是需要学好指针的。但是不能完全替代指针,比如一个链表,还是需要用到指针的。
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode;
//void LishPush(LTNode** phead, int x)
//这两者是等价的,我们可以通过引用改变指针指向
void LishPush(LTNode*& phead, int x)
{
}
之前我们使用链表,往里面插入数据需要传二级指针,现在有了引用就不需要传入二级指针,只需要传入指针的引用即可。
引用在使用的时候必须先初始化;一个变量可以有多个引用。
引用一旦引用一个实体,便不能引用其他实体。
int main()
{
int x = 0, z = 0;
int& y = x;
//是y变成z的别名呢?
//还是z赋值给y?
//是z赋值给x
y = z;
return 0;
}
注意这里y是x的别名,之后将z的值赋给x。
引用和指针的区别:
- 引用概念上定义一个变量的别名,指针存储一个变量地址。
- 引用在定义时必须初始化,指针没有要求。
- 引用在初始化时引用一个实体后,就不能在引用其他实体,二指针可以在任何时候指向任何一个同类型实体。
- 没有NULL引用,但有NULL指针。
- sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数。
- 引用加1即引用的实体加1,指针字节即指针向后偏移一个类型的大小。
- 有多级指针,但没有多级引用。
- 访问实体方式不同,指针需要显示解引用,引用编译器自己处理。
- 引用比指针使用起来相对更安全。
语法上引用不开空间,我们通过反汇编来观察:
可以发现底层都是指针,但是引用语法上不开空间。
因为就是起了一个别名,所以本质大小还是类型本身的大小。
注意事项:
我们先来看一段代码:
int* p = NULL;
int& r = *p;
//这里我们不能只看语法层
//这里就要看底层,是一个指针存放了空指针
一旦打印,就会崩溃,所以一定注意!
权限:
我们必须将权限单独来讲,每个变量都会存在权限。
//权限的方法
//m只读
//n变成m的别名,n的权限是可读可写
//权限只能缩小,不能放大
const int m = 0;
int& n = m;
因为取别名可以将权限放大。 权限只能缩小,不能放大。
int x = 0;
const int& y = x;
x++;//可以修改,只是不能够y去修改
我们再来观察一个代码:
int main()
{
double d = 12.34;
//类型转换会发生临时变量
int i = d;
int& y = d;//这句是错误的
const int& r = d;
return 0;
}
因为临时变量具有常性,所以我们只能添加const语法才正确。
内联函数:
如果我们要频繁使用一个函数时,如果这个函数是一个小函数,但是需要频繁使用,C语言一般是使用宏,这样可以提高效率。
但是宏是进行宏替换,很容易出现问题,一般是不建议使用宏去完成一个函数的,那么我们在C++该如何解决这个问题呢?
以inline修饰的函数叫做内联函数,编译是C++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,内联函数提升程序运行的效率。
在Debug版本下内联函数默认不展开,因为这样方便调试。
此时可以看到call,就说明还是去建立了函数栈帧。此时我们就需要去更改一些配置才能看到具体内联函数是如何操作的。
当内联函数比较大时,就不再会进行展开。
inline是一种以空间换时间的做法,如果编译器将函数当成了内敛函数处理,在编译阶段,会用函数体替换函数调用。
缺陷:可能会使目标文件变大 优势:少了调用开销,提高程序运行效率。
inline对于编译器而言只是一个建议,不用编译器关于inline实现机制可能不同,一般建议:将函数规模较小(函数不长,取决于编译器内部实现)不是递归切频繁调用的函数采用inline修饰,否则编译器会忽略inline特性。
也就是说,内联函数是否展开取决于编译器。
这里我们不考虑函数的栈帧,如果展开,编译出来的可执行程序会变大。
内联不能声明和定义分离!
因为在链接时会符号汇总,会去找函数的地址,但是内联函数是没有地址的(因为没有call),直接被展开,所以会报链接错误。所以使用内联函数时不要声明和定义分离。
auto:
auto可以替代写起来比较长的类型定义,简化代码。
int main()
{
int a = 10;
int b = a;
auto c = 'a';
cout << typeid(b).name() << endl;
cout << typeid(c).name() << endl;
return 0;
}
typeid可以帮助我们去看一个对象的类型。
int x = 10;
auto a = &x;
//指定必须是指针
auto* a1 = &x;
我们可以用auto来接受指针,可以在后面加上*也可以不加上*,但是一旦加上*,就指定它一定是一个指针变量。
auto不能作为函数的形式参数。
C++中有增强for循环,叫做范围for,我们可以利用auto来改善增强for循环。
int main()
{
int arry[] = { 1,2,3,4,5 };
for (auto x : arry) {
cout << x << " ";
}
return 0;
}
如果我们想利用增强for循环来改变数组内容,不能直接改变,因为临时变量是其拷贝,所以我们加上引用即可修改。
int main()
{
int arry[] = { 1,2,3,4,5 };
for (auto& x : arry) {
x += 1;
}
for (auto x : arry) {
cout << x << " ";
}
return 0;
}
在函数中,不能用增强for循环对数组进行遍历,因为传过去的是指针,其只能对数组使用。
void TestFor(int arry[])
{
for (auto e : arry) {
cout << e << endl;
}
}
int main()
{
int arry[] = { 1,2,3,4,5 };
TestFor(arry);
//for (auto& x : arry) {
// x += 1;
//}
//for (auto x : arry) {
// cout << x << " ";
//}
return 0;
}
因为C/C++追求效率,所以不支持传数组。
nullptr:
我们先看一段代码:
void f(int i)
{
cout << "f(int)" << endl;
}
void f(int* p)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f(NULL);
return 0;
}
因为NULL宏定义为0,所以此时调用的是一个函数。这就是C语言的缺陷。
f((int*)NULL);
此时只能这样修改。所以C++引入了nullptr关键字。
使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。
sizeof(nullptr)与sizeof((void*)0)所占字节数相同。为了提高代码健壮性,在后续表示指针空值是建议最好使用nullptr。
void f(int i)
{
cout << "f(int)" << endl;
}
void f(int* p)
{
cout << "f(int*)" << endl;
}
int main()
{
f(0);
f((int*)NULL);
f(nullptr);
return 0;
}
总结:
OK了老铁焖,我们已经基本上把所有C++基础语法学完了,当然你要有C语言基础,期待下一篇,就是类和对象!