在上一篇文章中我向大家介绍了关于C++的命名空间的用处以及一些,这篇内容主要是讲解有关C++入门的一些小知识,大家可以通过此文章初步进行一个了解,这些东西在之后的C++学习中都会有更多的妙用,如果有小伙伴感兴趣C++的命名空间,可以点击链接🔗C++的命名空间
一、缺省参数
缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调⽤该函数时,如果没有指定实参
则采⽤该形参的缺省值,否则使⽤指定的实参,缺省参数分为全缺省和半缺省参数。(有些地⽅把
缺省参数也叫默认参数)
cpp
//全缺省参数
void Func1(int a = 1, int b = 2)
{
std::cout << a << " " << b << std::endl;
}
//半缺省参数
//半缺省参数必须从右往左写
void Func2(int a, int b = 2)
{
std::cout << a << " " << b << std::endl;
}
int main()
{
Func1();
Func1(2);
Func1(2,3);
Func2(1);
Func2(1,3);
return 0;
}
大家可以对照一下结果,如果我没有调用时没有输入参数,就会直接输出函数定义时默认的数值,但要注意的时半缺省参数必须从右往左写,要不然编译器没有办法把数值进行匹配 。 比如下面这种写法,调用中的3如果是匹配的a的话,b就没有数值对应了。
cpp
void Func3(int a = 1, int b)
{
std::cout<< a << " " << b << std::endl;
}
int main()
{
Func3(3);
}
缺省参数不能声明和定义同时给出,比如下面的例子
cpp
test.h
void Func(int a = 1);//在头文件中已经给出了缺省参数
test.c
void Func(int a = 1)//再次给出便是错误
void Func(int a)//此做法则是正确的
{
std::cout << a << std::endl;
}
二、函数重载
C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同,可以是参数个数不同或者类型不同。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。C语⾔是不⽀持同⼀作⽤域中出现同名函数的。
cpp
#include<iostream>
using namespace std;
void Swap(int* pa, int* pb)
{
cout << "void Swap(int* pa, int* pb)" << endl;
}
//参数类型不同
void Swap(double* pa, double* pb)
{
cout << "void Swap(double* pa, double* pb)" << endl;
}
//参数个数不同
void Swap(int* pa, int* pb, double* pc)
{
cout << "void Swap(int* pa, int* pb, double* pc)" << endl;
}
//参数顺序不同
void Swap(int* pa, double* pc,int* pb)
{
cout << "void Swap(int* pa, double* pc, int* pb)" << endl;
}
int main()
{
int a = 10;
int b = 20;
double c = 30.0, d = 40.0;
Swap(&a, &b);
Swap(&c, &d);
Swap(&a, &b, &c);
//调用歧义
return 0;
}

值得注意的点是:
- 返回值不同不能构成重建,因为在调用的时候无法区分
- 函数重载中同名函数的形参列表(参数个数或参数类型或参数顺序)必须不同
为什么C语言不支持函数重载呢 ?
在编译过程中,C++编译器会进行以下几个步骤:
- 预处理:展开头文件、宏定义、条件编译等。生成预处理后的文件,例如 define.i 和 test.i。
- 编译:检查语法,生成汇编代码。例如,生成 define.s 和 test.s。
- 汇编:将汇编代码转换成机器码,生成目标文件。例如,生成 define.o 和 test.o。
- 链接:将多个目标文件链接成一个可执行文件。在链接时,编译器会根据修饰后的函数名来查找函数的地址。
在C++中,函数声明和定义的过程如下:
- test.cpp 中只有函数声明,没有函数的地址。
- 函数的定义在 define.cpp 中,经过汇编后生成目标文件 define.o,其中包含了函数的地址。
但是C语言是通过函数名来查找的,如果遇到同名却参数不同的函数,自然是很难定位到的,而C++则是根据修饰后的函数名来查找,这个修饰方法取决于编译器(有一些会带着参数一起查找)
三、引用
引⽤不是新定义⼀个变量,⽽是给已存在变量取了⼀个别名,编译器不会为引⽤变量开辟内存空间,它和它引⽤的变量共⽤同⼀块内存空间。例如你会网上"某些印度小哥"取名为"三哥"。
cpp
int main()
{
int a = 0;
//引用:b是a的别名
int& b = a;
int& c = a;
int& d = b;
//一个符号两用,地址是一致的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}
很明显可以看出来他们的地址都是一致的,所以在取别名并没有为他们额外开辟空间,同时在C++中"&"不仅表示引用,也可以表示取地址
引用也有一些要注意的点:
- 引用在定义的时候必须初始化
- 一个变量可以有多个引用
- 引用一旦引用一个实体,再不能引用其他实体
那么引用有什么用呢?再学习数据结构的时候很多小伙伴可能对指针的传参都是云里雾里的,引用传参则可以在一定环境上代替它!比如下面这个很常见的例子
cpp
void Swap (int a , int b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
Swap(x , y);
cout << x << " " << y << endl;
return 0;
}

可以看到这里是并没有交换成功的, 因为函数里的交换是没有被传回来了,形参只是实参的一份临时拷贝,以往我们都是通过指针来解决这个问题,很多朋友都搞不太清,现在我们可以用引用来解决,比如下面这种写法
cpp
void Swap(int &a, int &b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}

看这种就可以交换成功啦!引用还有一个要注意的点就是这个权限的问题,int的权限是可读可写,const是只读,所以int类型 就不能作为const类型的别名,这是权限放大。
cpp
int main()
{
const int m = 10;
int &n = m;//不可以,不能权限放大
const int& n = m;//可以,权限相同
int x = 0;
const int& z = x;//可以,权限缩小
const int* p1 = &m;
p1++;//可以++,const在*前面修饰的是*p1,在*后面修饰的是p1
int* p2 = p1;//不可以,const int*不能赋值给int*,权限放大
return 0;
}
还有一个权限的放大,是大家会忽视的,也就是算式运算结果存储、类型转换中产生的权限放大。
将 double 类型的 d 隐式转换为 int 类型,并赋值给 i。在这个过程中,d 的值会被截断为整数部分,即 12。隐式类型转换会产生一个临时变量,运算结果存储在这个临时变量中。因为 d 是 double 类型,编译器会创建一个临时的 int 类型变量来存储 d 的值(即 12),然后 r 引用这个临时变量。由于临时变量具有常量属性,所以引用必须是 const 类型。(常量属性也就是不能被修改,和const中的只读是一致的)!
cpp
int main()
{
double d = 12.34;
//隐式类型转换
//类型转换会产生临时变量,运算结果也是存储在临时变量中
int i = d;
int& r = d;//不可以,此时属于权限放大
const int& r = d;//如果不加const不可以,因为临时变量有常量属性;
return 0;
}
3.1、引用和指针的区别
刚刚我们有说到,引用在一定环境条件下是可以替代指针的,可以更好的理解
- 引用必须初始化,指针不需要初始化
- 引用初始化后不可以改变,指针可以改变指向
- 引用没有const,指针有const
- 引用没有NULL,指针有NULL
- 引用在编译时会被替换为常量指针
- 语法层面上引用不开空间,指针开空间
- sizeof的含义不同,引⽤结果为引⽤类型的⼤⼩,但指针始终是地址空间所占字节个数
cpp
int main()
{
int a = 10;
double b[] = { 1.2,2.3,3.4,4.5 };
int& x = a;//引用的本质是指针,r是a的别名
double* p = b;//指针p指向b的地址
int* ptr = NULL;
int& r = *ptr;//这里并没有对指针进行解引用
//cout << r << endl;//会报错的原因是在打印的时候会对指针进行解引用,但引用不能为NULL
cout << sizeof(x) << endl;
cout << sizeof(int&) << endl;
cout << sizeof(p) << endl;
}
大家可以对照一下别名和指针的内存大小区别,以及上面所说的我们单纯只是将指针赋值给别名r的时候是不会报错的,因为这个时候没有对指针解引用,他只是给这个空指针换了个名字,但是当我们要输出的时候,就会对指针进行解引用了,引用是没有NULL的!

四、内联函数
我们在项目开发的时候可能会有一些函数会进行频繁调用,比如我们在进行排序算法的时候我们经常就会用到swap函数,但在这种时候我们就会建立很多函数栈帧从而造成性能的损失,在C语言中我们通常是通过宏来解决这个问题,但是宏的使用其实并不简单,很多初学者很容易踩坑。在C++中我们就利用内联函数来替代C语言中的宏。
cpp
inline void Swap(int& a, int& b)
{
int temp = a;
a = b;
b = temp;
}
int main()
{
int a = 1, b = 2;
Swap(a, b);
cout << a << " " << b << endl;
return 0;
}
但是内联函数也有一些坏处
- 函数内部语句很多不会展开,内联对于编译器来说是一个建议
- 可能会使目标文件变大
- 内联不能定义和声明分离,分离会导致链接错误,因为内联被展开,没有地址
五、auto的便利用法
大家以前遍历数组是不是这样的
cpp
int main()
{
int array[] = { 0,1,2,3,4,5,6 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
array[i] *= 2;
}
for (int i = 0; i < sizeof(array) / sizeof(array[0]); ++i)
{
cout << array[i] << " ";
}
cout << endl;
}
这多麻烦,今天C++给您行个方便,看下面这个是不是代码一下少写了不少,这就是C++的便利
cpp
int main()
{
int array[] = { 0,1,2,3,4,5,6 };
for (auto x : array)//auto自动识别变量类型
{
cout << x << " ";
}
cout << endl;
return 0;
}
注意!
- auto不能作为函数的参数
- auto不能定义数组
- 不能在同一行定义不同类型的变量,auto无法推断
好啦今天的文章就到这里啦,这些都只是一些C++很入门的东西,C++在很多地方对C语言进行了优化,让语言使用起来更加的舒适优化,但是C++学起来还是很有难度的,希望今天的入门小知识有帮助到你,这么个宝藏博客还不点个关注和小心心嘛?