前言
这一篇文章和上一篇文章都是记录C++入门的
1.纠正错误
首先我的文章的上一篇有一个地方有问题,就是“所以定义了一个没有说明的变量,它的访问顺序先局部域,再全局域,最后才是这个展开的命名空间“。这句话有问题,展开的命名空间和全局域应该是同一级的,意思是这两个没有什么优先级
2.函数重载
接下来我们讲一下函数重载,所谓函数重载,就是在同一作用域中,函数名相同, 函数的参数的类型或顺序或数量不一样的函数就是函数重载。有了函数重载的话,就可以使用相同的函数名,来实现不同的功能了。
2.1实例
函数重载的一个重要作用就是可以根据参数的不同与区别来进行不同的函数,这一个是参数的类型不同
这一个是参数不同类型的顺序不同
这个告诉了我们,有多个函数构成函数重载,优先看参数,参数不同就选择不同的函数,如果就只有那一个函数,可能还会强制类型转换来匹配,或者就是不匹配
这个又说明了,函数重载与返回值是无关的,这里就相当于是重定义了,编译器·运行时不知道选哪个函数来运行,所以出错了
这个主要是从参数的个数不同来建立重载函数的
这两个函数是构成函数重载的,因为一个有参数,一个没有参数,函数语法上的建立没有问题,主要是第二个函数是缺省参数,所以不传参时函数不知道调用哪个函数,所以就报错了。
虽然这个程序运行没有什么问题,但这两个函数不是重载函数,因为他们在不同作用域,就算展开作用域,也还是不同作用域
2.2为什么C++支持重载,C语言不支持重载
因为在编译链接的时候不是要一个函数对应一个地址吗,C语言就是一个函数名对应一个地址,所以如果设置多个同名函数就会造成一个函数名有多个地址,这是不行的。
而在C++中有函数名的命名规则,就是相同名字不同参数所对应的函数名是不一样的,这样不同函数名对应不同地址就不会出错
3.引用
所谓引用,就是给变量取别名
取别名的话,那么a就是b,b就是a,a和b指向的同一个空间,一个改变,另一个也跟着改变
还可以给别名取别名
这个就相当于,给一个人取外号,外号就是他,他就是那个外号的人
还要注意,引用类型要和实体是同一个类型的,不然一般会出错,后面有特例
引用必须要初始化
取别名时只能在初始化的时候取别名,而且后面还不能更改别名所指向的实体了,不然那只是单纯的赋值
int main()
{
const int a = 10;
//int& ra = a; // 该语句编译时会出错,a为常量
//因为这个是权限的放大,本来是不能改变a空间的值的,现在可以了,这就是权限的放大,这是不行的
const int& ra = a;//这个就可以,因为这个是权限的平移
return 0;
}
int main()
{
int a = 10;
const int& ra = a;
return 0;
}
这个就是权限的缩小,是可以的,权限的平移与缩小都是可以的
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, * PNode;
void ListPushBack(PNode& phead, int x)
{
if (phead == NULL)
{
// phead = newnode;
}
}
这个引用的最大用处就是指针这里了,以前要改变实参的话就要传地址,现在可以直接用别名来改变了
除了引用有权限的放大与缩小,还有指针的放大与缩小,普通变量就是赋值罢了
const在前面,const修饰的就是*p1,p1不能改,意思就是a不能改,这是权限的平移,第二个p2可以改,这是权限的放大,第三个也是权限的放大
第一个是权限的缩小,第二第三都是权限的平移,都是ok的
在这里不管是10还是10+3,等等最后的结果都是一个常数也是常量,都会存在一个常量里面,是const int 的,所以取的别名其实是这个隐式的常量的别名,所以第一和第三其实是权限的缩小了,是不行的
在这里a会先发生隐式类型转换,而这里隐式类型转化的话,又会生成一个int的常量来存储,所以第一个别名又是权限的缩小了。
强制类型转换的话,也是和隐式类型转换是一样的,也会生成一个常量
p1语法上是不会消耗空间的,因为你定义再多的别名,最后指向的都是那块空间,消耗的空间没有增长,而p2就会消耗空间,消耗空间去存储地址
但是别名实际上是消耗了空间的,实际上取别名的过程中会存储到a的地址,也是会消耗空间的
但这并不代表sizeof(别名)就是那个地址的大小,sizeof(别名)和·sizeof(本体)肯定是相同的
因为取别名的实质是存储了(*p)的地址,所以并不会真正访问空指针,所以没问题
但是cout的时候就有问题了,因为会去访问空指针
因为取别名的实质是存储地址,所以取别名额外消耗的空间就是那个地址,顶多也就8个字节,如果面对大的类型时,所以取别名往往要比值拷贝更好
还有就是不存在多级引用的概念
4.内联函数
在面对小函数的多次使用时,这样会消耗很多的栈空间,这样的话我们引入了内联函数
关键字为inline
这个内联函数主要就是在访问的地方直接展开,就和头文件的展开差不多,这个的好处就是不会开辟栈空间,坏处就是主函数的长度增加了,会增加add这个函数的那几行,就这样,如果内联函数太长了,是不会直接展开的,还是会开辟栈空间,内联函数的控制权是在编译器手上的,就算你写了inline也是这样
还有一个缺点就是,因为是直接展开的,所以在调试的时候,也不好调试,不方便,调试的时候就会直接跳过
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
还有一个就是内联函数不支持定义与声明分开,因为写了inline的话就表示这个函数没有地址,就会直接展开,又因为链接的时候需要地址,所以就会出错
总结C++有哪些技术替代宏?
- 常量定义 换用const enum
- 短小函数定义 换用内联函数
5.auto关键字
auto关键字这个的作用就是在定义变量的时候,根据后面的赋值来决定变量是什么类型,后面是10,那么auto表示的就是int
虽然说typedef也有相同的效果,但是呢,每次都typedef你不麻烦吗,而且这还有一个特例,说明了typedef不太好
typedef char* pstring;
int main()
{
const pstring p1; // 编译成功还是失败?
const pstring* p2; // 编译成功还是失败?
return 0;
}
首先, typedef char* pstring; 这个定义使得 pstring 成为了指向字符的指针类型的别名。
对于 const pstring p1; ,按照上述的类型定义,它实际上被解释为 char * const p1; 。这意味着 p1 是一个指向字符的常量指针,即指针本身的值不能被修改,但是它所指向的字符内容是可以修改的。然而,在 C 和 C++ 中,通常更常见和有用的是让指针指向的内容为常量,而不是指针本身为常量。所以这种声明不符合常见的编程需求和语法习惯,导致编译失败。
而对于 const pstring* p2; ,同样根据类型定义,它相当于 const char* * p2; 。这表示 p2 是一个指针,它指向的是另一个指针,而那个被指向的指针是一个指向常量字符的指针。这种声明在语法上是合法的,并且在实际编程中也有其应用场景,比如在处理一系列指向常量字符串的指针时,所以能够编译成功。
注意正是因为auto是根据后面的赋值来确定的,所以变量一定要初始化
void TestAuto()
{
auto a = 1, b = 2;
auto c = 3, d = 4.0; // 该行代码会编译失败,因为c和d的初始化表达式类型不同
}
还有就是在一行定义多个变量的时候,一定要定义相同类型的变量,不然不知道auto指的是什么,所以第二行有错误
然后就是typeid这个东西可以求某个变量的类型,就是这样用的,记住就可以了
注意auto不能做函数参数,因为这样谁都可以传了,怎么可以!
还有就是auto不能定义数组
接下来介绍一个数组遍历的新方法
这个东西叫做范围for,是新语法
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8 };
for (auto e : arr)
{
cout << e << ' ';
}
cout << endl;
for (auto& e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e << ' ';
}
return 0;
}
for (auto e : arr)这个就相当于把arr数组的每个值arr[i]都这样给e:auto e=arr[i],从开头开始,到末尾结束,这个是自动的
for (auto& e : arr)这个就相当于auto &e=arr[i],这样的话改变e就可以改变数组的每个值了
6.C++中的空指针
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
这段话的意思就是在C++中NULL就是整型0,所以在C++中NULL无法表示空指针
所以要表示空指针的话就要用(void*)0,但这样太麻烦了,就用nullptr了