当学习了C语言之后,很多的小伙伴都想进一步学习C++,但两者有相当一部分的内容都是重叠的,不知道该从哪些方面开始入门C++,这篇文章罗列了从C到C++必学的入门知识,学完就算是踏入C++的大门了。
1. 命名空间
写C的时候,偶尔可能不小心就和库函数冲突了,又或者当两个程序员以相同的名字命名变量或函数,就会发生冲突。C++就提出了一个很好的解决方案——命名空间。
命名空间的关键字是namespace,定义方式如下:
【定义方式】
namespace N1
{
int a = 3;
int b = 4;
int Add(int x1, int x2)
{
return x1 + x2;
}
struct Node
{
struct Node* next;
int val;
};
namespace N2
{
int c = 0;
int d = 0;
}
}
从示例中可以看出,命名空间中可以包含变量、函数、类型以及嵌套另一个命名空间
【使用方式】
1. 命名空间+作用域限定符
int main() {
printf("%d\n", N1::a);
return 0;
}
2. using将命名空间中某个成员引入
using N1::b;
int main() {
printf("%d\n", N1::a);
printf("%d\n", b);
return 0;
}
3. using namespace 变量空间
using namespace N1;
int main() {
printf("%d\n", a);
printf("%d\n", b);
return 0;
}
2. 输入输出
其实C语言的printf/scanf在C++中还可以使用,不过C++有独有的输入输出方式,就是cin/cout
【使用说明】
1. 必须包含iostream头文件,注意是<iostream>
2. 需要使用命名空间std,因为cin/cout在其中的(日常练习可以这么做,但做项目的时候最好使用 std::cin 的方式展开)
3. 使用时要用<<流插入操作符/>>流提取操作符,不知道是什么没关系,看示例是怎么用的就行,cout用<<,cin用>>
4. 变量之间也是用<<或>>连接,如果需要换行用endl
【示例】
#include <iostream>
using namespace std;
int main()
{
cout << "hello world!" << endl;
return 0;
}
【解惑】
我们有了printf和scanf为什么还要学习cin和cout呢,因为其可以自动识别变量的类型,相对来说就方便了很多,不用考虑太多。不过cin/cout相对于前者慢一些,在大量数据需要输入输出时就会有较大的时间差距。
3. 缺省参数
声明或者定义函数时为函数参数指定一个缺省值,当函数没有传递对应的参数时,就用缺省参数来代替(有点像备胎的感觉doge)
#include <iostream>
using namespace std;
void Func (int a = 10)
{
cout << a << endl;
}
int main()
{
Func(); //输出10
Func(100); //输出100
return 0;
}
【分类】
全缺省参数:所有形参都加上了缺省参数
void Func(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
int main()
{
Func(9, 8, 5);
Func(9, 8);
return 0;
}
半缺省参数:只有部分参数给缺省值
void Func(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl;
}
//错误写法
//void Func(int a = 10, int b, int c = 30)
//void Func(int a = 10, int b = 20, int c)
//void Func(int a = 10, int b, int c)
int main()
{
Func(9);
Func(9, 8);
Func(9, 8, 5);
return 0;
}
注意:
1. 半缺省参数只能从右往左给,不能跳跃给,也是为了避免传错参数,出现歧义
2. 缺省参数不能同时在定义和声明中出现,一般在声明中给
3. 缺省值必须是全局变量或常量
4. 函数重载
C++中允许在同一个作用域中出现参数列表不同(参数个数或类型不同)的同名函数,称这种现象为函数重载
【示例】
#include <iostream>
using namespace std;
//参数个数不同
void f1(int x, int y)
{
cout << "void f1(int x, int y)" << endl;
}
void f1(int m)
{
cout << "void f1(int m)" << endl;
}
//参数类型不同
void f2(int m)
{
cout << "void f2(int m)" << endl;
}
void f2(double m)
{
cout << "void f2(double m)" << endl;
}
//参数类型顺序不同,本质就是类型不同
void f3(int x, double y)
{
cout << "void f3(int x, double y)" << endl;
}
void f3(double x, int y)
{
cout << "void f3(double x, int y)" << endl;
}
int main()
{
f1(10, 20);
f1(10);
cout << endl;
f2(10);
f2(1.0);
cout << endl;
f3(10, 1.0);
f3(1.0, 10);
return 0;
}
【原理】
C++的编译器会对函数进行修饰,一种函数变量类型对于一种编码,所以不同的变量类型或者个数对应不同的汇编代码,所以函数名相同并不影响其分别不同的函数。
而C语言中,相同的函数名是区分不开的。
(此部分需要对程序的翻译和执行部分有一定的了解,可以暂时跳过)
5. 引用
引用是对已经创建的变量起别名,操作符是&
【示例】
#include <iostream>
using namespace std;
int main()
{
int a = 1;
int& b = a;
cout << a << endl;
cout << b << endl;
//打印结果都是1
cout << &a << endl;
cout << &b << endl;
//打印出来是同一个地址,也就意味着在同一个空间
return 0;
}
结果如下:
所以当其中一个加的时候,另一个也加,效果如下:
注意:
1. 引用和实体的类型要相同,鲁迅和周树人都是人
2. 引用在定义时必须初始化
3. 一个变量可以多个引用,也就是可以多个别名
4. 引用一个实体就不能再引用其他的实体,鲁迅是周树人的别名,就不能再用“鲁迅”代指张三了
5. 引用不能作函数局部变量的返回值,因为返回值的空间在退出函数之后就销毁了,而引用与它共用一块空间,所以此时为非法访问空间。当然,如果是全局变量/函数外局部变量/静态变量/堆上变量也是可以的作为返回值的。
【使用方式】
1. 输出型参数
#include <iostream>
using namespace std;
void Swap1(int& x, int& y)
{
int temp = x;
x = y;
y = temp;
} //节省拷贝的空间,在传大的对象时对比更加明显
void Swap2(int* m, int* n)
{
int temp = *m;
*m = *n;
*n = temp;
} //需要额外开辟一块空间存临时变量
int main()
{
int a = 1, b = 2;
Swap1(a, b);
cout << a << " " << b << endl;
int c = 1, d = 2;
Swap2(&c, &d);
cout << c << " " << d << endl;
return 0;
}
2. 函数返回值(注意返回局部变量时不可用)
#include <time.h>
#include <iostream>
using namespace std;
struct A
{
int a[10000];
};
A a;
A TestFunc1()
{
return a;
}
A& TestFunc2()
{
return a;
}
int main()
{
size_t begin1 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc1();
size_t end1 = clock();
size_t begin2 = clock();
for (size_t i = 0; i < 100000; ++i)
TestFunc2();
size_t end2 = clock();
cout << "TestFunc1 time:" << end1 - begin1 << endl;
cout << "TestFunc2 time:" << end2 - begin2 << endl;
return 0;
}
这效率,赢麻了!
有小伙伴又要问了,引用这么香,谁还用指针呀?
那我们就得来看看指针和引用的区别了:【面试考点】
语法:
- 引用是别名,不开空间,指针是地址,需要开空间存地址
- 引用必须初始化,指针可以初始化也可以不初始化
- 引用不能改变指向,指针可以(不可替代的关键)
- 引用相对更安全,没有空引用,但是有空指针,容易出现野指针,但是不容易出现野引用
- sizeof(引用为引用类型的大小,指针为地址的大小)、++(引用实体+1,指针偏移一个类型的大小)、解引用访问(引用不用解引用)等方面的区别
底层:
汇编层面上,没有引用,都是指针,引用编译后也转换成指针了
总结:C++的引用,对指针使用比较复杂的场景进行一些替换,让代码更简单易懂,但是不能完全替代指针,因为引用定义后,不能改变指向
【常引用】
如果我需要一个不可被修改的别名,就会使用到常引用
//代码一:正常引用(权限平移)
int a = 10;
int& ra = a;
//代码二:会报错,本来我设的变量不可被改,但是你的引用可以改,相当于给它的权限过大了
const int b = 10;
int& rb = b;
//代码三:常引用,相当于创建了一个不可修改的别名(权限缩小)
int c = 10;
const int& rc = c;
//代码四:d转换成int需要生成临时变量,再将临时变量赋给rd引用,
//而临时变量是常量,所以rd要用常量引用,此代码没有const是编译错误的
double d = 3.14;
const int& rd = d;
6. 内联函数
一般来说,函数调用都会建立栈帧,而如果有方式可以避免栈帧的调用,可以大大提高运行的效率。
C++引入了一个关键字inline,以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开(函数调用替换为函数体),没有函数调用建立栈帧的开销,从而大大提升运行效率。
【示例】
inline int Add(int x, int y)
{
return x + y;
}
int main()
{
int ret = Add(3, 4);
cout << ret << endl;
return 0;
}
其实没什么好说的,就是在函数前加上inline。
不过,编译器会判断此代码是否大小合适,如果比较大的话还是会建立栈帧,避免在调用处展开导致程序膨胀。
注意:
1. 不能是递归的函数,且最好规模较小、频繁调用的函数。
2. 不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到。
【拓展】
【面试考点】如果在头文件中包含了一个函数的声明和定义,导致出现链接错误,该如何处理?
1. 声明和定义分离
2. 用static修饰函数,链接属性只在当前文件可见,不会进符号表(大函数)
3. inline修饰,没有进符号表,不会出现链接冲突
有小伙伴就要问了,好像C语言里的宏也可以做到类似的效果(不建立栈帧就能调用)呀?
但是宏也是有缺点的,详细的内容可看这篇文章-链接
在C++中,有很多设计都可以替代宏:const,enum,inline
7. auto关键字
在C++中,有的类型的名字会很长,所以很容易会出错,而这就引出了auto关键字
这个关键字的作用就是自动识别类型
当然,除了用在比较长的类型名上,还可以用于for循环,这个也是C++特有的
示例如下:
int main()
{
int array[] = { 1,2,3,4,5 };
for (auto& e : array)
{
e *= 2;
}
for (auto e : array)
{
cout << e << " ";
}
return 0;
}
上面这段代码等价于:
int main()
{
int array[] = { 1,2,3,4,5 };
for (int i = 0; i < sizeof(array) / sizeof(array[0]); i++)
{
array[i] *= 2;
}
for (int* p = array; p < array + sizeof(array) / sizeof(array[0]); p++)
{
cout << *p << " ";
}
return 0;
}
可见这里可以省去了不少的代码,不过要注意如果不是遍历数组就不能这么操作了
【注意点】
1. auto声明指针类型时,用auto和auto*没有区别,但用auto声明引用类型时必须加&。
2. auto不能作为函数参数和返回值,也不能用来声明数组
8. 空指针nullptr
NULL可作常量0,也可作无类型指针的常量,
一般情况下,NULL会被编译器认为是0常量,但是我们想用它的时候往往是当作指针来用,为了避免会出现麻烦的情况,所以C++又设计了一个nullptr指针,就是表示指针空值,即(void*)0
这篇内容很多,可以收藏下来慢慢看~感谢大家的点赞和支持👍