硬核两万字文章带你C++入门
Posted 小赵小赵福星高照~
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了硬核两万字文章带你C++入门相关的知识,希望对你有一定的参考价值。
C++入门
文章目录
C++关键字
C语言关健字32个,C++关键字63个关键字
asm | do | if | return | try | continue |
---|---|---|---|---|---|
auto | double | inline | short | typedef | for |
bool | dynamic_cast | int | signed | typeid | public |
break | else | long | sizeof | typename | throw |
case | enum | mutable | static | union | wchar_t |
catch | explicit | namespace | static_cast | unsigned | default |
char | export | new | struct | using | friend |
class | extern | operator | switch | virtual | register |
const | false | private | template | void | true |
const_cast | float | protected | this | volatile | while |
delete | goto | reinterpret_cast |
C++是在C的基础上发展起来的,C++是兼容C的大多数的语法
命名空间
C++是怎么输出hello world的呢?和C语言有什么区别呢?
#include<iostream>
using namespace std;
int main()
{
cout<<"hello world"<<endl;
return 0;
}
我们创建一个C++文件后,写出上面代码就会在屏幕上打印hello world,那么上面代码中的using namespace std;是什么意思呢?这就是我们要讲的命名空间。
C++增加命名空间是为了解决C语言的不足,在一段C语言代码中,我们编译下面的代码会报错:
#include<stdio.h>
#include<stdlib.h>
int rand =10;
int max=10;
int main()
{
printf("%d\\n",rand);
return 0;
}
那么为什么会报错呢?是因为我们包含了stdlib.h这个头文件
这是一个命名重定义的问题,在我们编写C语言代码时,我们定义变量名时,可能与库函数中的命名冲突了,实际大型项目开发,还存在同事之间定义的变量、函数、类型命名冲突等等
所以为了弥补C语言的不足,C++提出了命名空间来解决命名冲突的问题
那么命名空间是怎么定义的呢?
定义命名空间,需要使用到namespace关键字,后面跟命名空间的名字,然后接一对{}即可,{}中即为命名
空间的成员。注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中
#include<stdio.h>
#include<stdlib.h>
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域,名字叫Z
{
//可以定义变量、函数、类型
int rand = 10;
}
int main()
{
printf("%d\\n",Z::rand);//::域作用限定符
return 0;
}
::是域作用限定符,通过这个我们可以找到命名空间中的定义的变量或者其他东西,这样我们就可以定义rand,并可以成功的打印它
我们也可以在里面定义函数:
#include<stdio.h>
#include<stdlib.h>
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域,名字叫Z
{
//可以定义变量、函数、类型
int Add(int left,int right)
{
return left+right;
}
}
int main()
{
Add(1,2);//直接调用他找不到,因为他只会在全局里面找,不会去域里面
Z::Add(1,2);
return 0;
}
这里需要注意的是直接调用他找不到,因为他只会在全局里面找,不会去域里面,所以我们需要利用域作用限定符去调用Add函数
命名空间还可以嵌套定义:
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域
{
//定义变量、函数、类型
int rand = 10;
int Add(int left,int right)
{
return left+right;
}
namespace S//会将外面的定义的变量隔离开来,定义了一个命名空间域
{
int Sub(int left,int right)
{
return left-right;
}
}
}
int main()
{
Add(1,2);//直接调用他找不到,因为他只会在全局里面找,不会去域里面
Z::Add(1,2);
Z::S::Sub(1,2);
return 0;
}
我们在调用嵌套的命名空间的函数时,比如我们现在调用Sub函数,我们需要用Z::S::Sub(1,2);去调用该函数
在一项工程当中不同文件的命名空间的定义名字可以相同,并且该命名空间中的定义编译器会合并在一起,比如:
在Add.cpp这个文件中我们定义了一个命名空间
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域
{
//定义变量、函数、类型
int rand = 10;
}
在Add.h中我们定义了命名空间
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域
{
//定义变量、函数、类型
int Add(int left,int right)
{
return left+right;
}
}
编译器会将这两个文件中定义的空间进行合并:
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域
{
//定义变量、函数、类型
int rand = 10;
int Add(int left,int right)
{
return left+right;
}
}
那么我们如何使用命名空间的东西呢?
有三种方式:
- 1、全部直接展开到全局
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域
{
//定义变量、函数、类型
int rand = 10;
int Add(int left,int right)
{
return left+right;
}
}
using namespace Z;//将Z命名空间展开
int main()
{
Add(1,2);
return 0;
}
优点是用起来方便。缺点是把自己的定义暴露出去了,导致命名污染
using namespace std;//std是包含C++标准库的命名空间
std是包含C++标准库的命名空间,这里其实就将C++标准库展开了,这就解释了我们开头打印hello world时,前面为什么有一个using namespace std;这里就很好的解释了这个代码的意思
- 2、访问每个命名空间中的东西时,指定命名空间
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域
{
//定义变量、函数、类型
int rand = 10;
int Add(int left,int right)
{
return left+right;
}
}
int main()
{
Z::Add(1,2);
return 0;
}
优点:不存在命名污染。缺点:用起来麻烦,每个都得去指定命名空间
- 3、将命名空间中常用的展开
namespace Z//会将外面的定义的变量隔离开来,定义了一个命名空间域,名字叫Z
{
//可以定义变量、函数、类型
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
}
int main()
{
using Z::Add;
return 0;
}
不会造成大面积的污染,也可以解决每个都指定命名空间的问题,这是一个将1和2折中的解决方案
C++输入&输出
这又回到了我们开始提到的C++是怎么输出hello world到屏幕上的,我们来看下C++是如何来实现的:
#include<iostream>
//展开常用的
using std::cout;
using std::endl;
int main()
{
cout<<"hello world"<<endl;
return 0;
}
按照上面我们所讲的,std是一个命名空间,命名空间中有我们要使用的cout和endl,所以我们这里只将常用的展开,但是在日常学习中,我们并不需要像项目中那么规范,我们可以直接将std命名空间展开。
#include<iostream>
//展开常用的
using namespace std;
int main()
{
cout<<"hello world"<<endl;
return 0;
}
我们了解了输出,那么C++是如何进行输入的呢?我们使用cin标准输入,看下面代码:
#include<iostream>
//展开常用的
using namespace std;
int main()
{
int n;
cin >> n;//>>输入运算符 流提取运算符
int* n = (int*)malloc(sizeof(int)*n);
for(int i=0;i<n;i++)
{
cin>>a[i];
}
for(int i=0;i<n;i++)
{
cout << a[i]<<" ";//<<输出运算符/流插入运算符 可以连续输出
}
cout<<endl;
//等价于
cout<<"\\n";
}
注意:使用cout标准输出(控制台)和cin标准输入(键盘)时,必须包含< iostream >头文件以及std标准命名空
间。早期标准库将所有功能在全局域中实现,声明在.h后缀的头文件中,使用时只需包含对应头文件即可,后来将其实现在std命名空间下,为了和C头文件区分,也为了正确使用命名空间,规定C++头文件不带.h;旧编译器(vc 6.0)中还支持<iostream.h>格式,后续编译器已不支持,因此推荐使用+std的方式。我们在输出时可以连续输出,cout<<endl;实际上就等价于cout<<"\\n"。
另外C语言输入输出时需要指定类型的,C++不用指定类型可以自动识别类型:
double* n = (double*)malloc(sizeof(double)*n);
for(int i=0;i<n;i++)
{
cin>>a[i];//自动识别类型
}
int main()
{
int i=2;
double d = 1.111;
int* pi = &i;
cout<<i<<endl
cout<<d<<endl
cout<<pi<<endl;
return 0;
}
在写C语言printf和scanf函数时,前面我们需要指定输入输出的格式,而C++则是自动识别类型。
而在下面的场景中,用printf会更好一点
struct Student
{
char name[10];
int age;
};
int main()
{
struct Student s = { "张三", 18 };
cout << "名字:" << s.name << " " << "年龄:" << s.age << endl;
printf("名字:%s 年龄:%d\\n", s.name, s.age);
return 0;
}
缺省参数
缺省参数概念
所谓缺省参数,顾名思义,就是在声明函数的某个参数的时候为之指定一个默认值,在调用该函数的时候如果采用该默认值,你就不需要传参。可以传参数,也可以不传,如果不传,函数参数用缺省的
我们看下面代码:
#include<iostream>
using namespace std;
void TestFunc(int a = 0)//参数缺省值
{
cout<<a<<endl;
}
int main()
{
TestFunc();//不传会用默认值
TestFunc(10);//传了就缺省参数就没用了
return 0;
}
我们可以看到没有传参的使用了缺省值,而传参的就忽略缺省值了,只考虑传过来的参数
那么缺省参数有什么用呢?
我们在数据结构中,设计栈这个数据结构时:
void StackInit(struct Stack* ps, int defaultCP)
// 假设我明确知道这里至少要存100个数据到st1里面去
struct Stack st1;
StackInit(&st1, 100);
如果我们假设明确知道一个栈st1最少使用100个空间,我们给栈的容量初始化为100,所以这时我们在栈初始化这个接口引入一个参数defaultCP,我们想要弄100个容量,我们直接传参100就可以了。
但是当我们不能明确知道栈st2的需要使用的空间,如果上面不设置缺省参数,那么我们必须要传一个值进去,那我们传的值大了的话,会造成空间浪费,传的小了的话会不够用,那么此时这个缺省参数就派上用场了,我们这样定义:
void StackInit(struct Stack* ps, int defaultCP = 4)
struct Stack st2;
StackInit(&st2);
不传值时默认它为4,不够了再进行扩容就可以了
缺省参数分类
全缺省参数
void TestFunc(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
全缺省参数顾名思义就是全部都有缺省参数,我们在传参时不能间隔着传,比如:
TestFunc(100, ,50);//这样是错误的
编译器是不允许这样进行传参的
半缺省参数
void TestFunc(int a, int b = 10, int c = 20)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
cout<<"c = "<<c<<endl;
}
int main()
{
//没有缺省的必须传参
//半缺省参数必须从右往左依次来给出,不能间隔着给
TestFunc(1);
TestFunc(1,2);
TestFunc(1,2,3);
return 0;
}
注意:
没有缺省的必须传参
比如这上面的a参数必须要传参
半缺省参数必须从右往左依次来给出,不能间隔着给
比如:
void TestFunc(int a = 30, int b = 10, int c)
这样就是错误的,没有按照从右往左的顺序
也不能这样:
void TestFunc(int a = 30, int b, int c = 10)
缺省参数不能在函数声明和定义中同时出现
//a.h
void TestFunc(int a = 10);
//a.c
void TestFunc(int a = 20)
{}
// 注意:如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
如果生命与定义位置同时出现,恰巧两个位置提供的值不同,那编译器就无法确定到底该用那个缺省值。
缺省值必须是常量或者全局变量
void TestFunc(int a = x);//error
C语言不支持(编译器不支持)
C语言是不支持缺省参数的。
函数重载
函数重载概念
重载函数是函数的一种特殊情况,为方便使用,C++允许在同一范围中声明几个功能类似的同名函数,但是这些同名函数的形式参数(指参数的个数、类型或者类型顺序)必须不同,也就是说用同一个函数完成不同的功能。
//函数重载
int Add(int left, int right)
{
return left+right;
}
double Add(double left, double right)
{
return left+right;
}
long Add(long left, long right)
{
return left+right;
}
int main()
{
Add(10,20);//会调用对应参数类型的函数
Add(10.0,20.0);
Add(10L,20L);
return 0;
}
函数重载面试:为什么C++支持函数重载,而C语言不支持?
C编译器,直接用函数名关联,当函数名相同时,它无法区分
C++如何支持重载呢?
函数名修饰规则:不能直接用函数名对函数进行修饰,代入参数特点修饰,函数名相同,只要参数不同,修饰出来的名字就不同,就可以区分两个函数了,就支持重载了,把定义在文件中的函数调用,地址找到(符号表里找),链接在一起,所有.o文件进行合并,生成一个执行文件。
函数重载:
要求参数不同,因为参数不同修饰出来的名字就不同
编译器能不能实现函数名相同参数相同返回值不同,就能构成重载?
不行
int func();//-> _Z4ifunc double func();//-> _Z4dfunc
如果把返回值带进修饰规则,那么编译器层面是可以区分的。
但是语法调用层面,无法区分,带有严重的歧义!
func();调用时,到底是调用哪个呢?这是不知道的,所以不能
名字修饰
extern"C"
C++实现编写成动态库或者静态库,写一个C++的程序去调用这个库是没问题的,但是我们写一个C程序就不行,程序是用因为链接时会有问题,比如静态库有一个函数void* tcmalloc(size_t n) (谷歌提供的更高效替代malloc的库)
C程序在链接时,直接用函数名tcmalloc去找函数的地址,因为C++有名字修饰,而生成的符号表是:0x662521:_Z8tcmallocui,而C++是用_Z8tcmallocui去找的,C++可以找到,因为该动态库是C++写的,而C语言程序就找不到
那么有什么方式能让C程序和C++的程序都能用这个C++的库呢?
C++就出现了extern"C",在声明tcmalloc这个函数时在前面加extern"C" void* tcmalloc(size_t n),此时符号表按C语言修饰规则就成为了0x662521:tcmalloc,这时C程序就能正确的找到了,C++程序中有tcmaloc函数的声明,发现有extern"C",就按C语言修饰规则去找,因为C++兼容C,所有C的修饰规则它也是知道的。
总结:
当C的程序和C++的程序都想调用一个C++实现的模块时,C++可以调,但C语言不能调,那么两个都想调用时,我们就在C++实现的该模块里面函数声明时在前面加extern"C",那么生成的符号表里面就不对这个函数进行修饰了,这时C语言实现的程序就可以调用它了,紧接着C++程序在调用时,因为C++是兼容C的,发现函数的声明有extern"C",所以就在链接的时候按C的规则去找这个模块的函数
面试题:
下面两个函数能不能构成函数重载?
void TestFunc(int a = 10)
{
cout<<"void TestFunc(int)"<<endl;
}
void TestFunc(int a)
{
cout<<"void TestFunc(int)"<<endl;
}
void TestRef()
{
int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\\n", &a);
printf("%p\\n", &ra);
}
不行,重载必须要函数参数列表不同,因为这样才在名字修饰时有所区别,能够区分函数。
C语言中为什么不能支持函数重载?
C编译器,直接用函数名关联,当函数名相同时,它无法区分
C++中函数重载底层是怎么处理的?
在链接阶段,有符号表的合并与重定义,那么函数名相同那么他们重载函数符号表会不会冲突呢?答案是不会的,C++有自己的名字修饰规则,比如重载函数参数类型一个为int,另一个为float,在名字修饰时,就会将这些信息代入进去,这样就区分了两个函数。
C++中能否将一个函数按照C的风格来编译?
可以,在该函数前面加extern"C"即可,C++程序中有函数的声明,发现有extern"C",就按C语言修饰规则去找,因为C++兼容C,所有C的修饰规则它也是知道的
引用
引用概念
引用并不是新定义一个变量,而是给已存在的变量取了一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用同一块内存空间
类型& 引用变量名 = 引用实体;
#include<iostream>
using namespace std;
int main()
{
int a=0;
int& ra = a;//ra是a的引用,引用也就是别名,a再取了一个名称ra
return 0;
}
我们可以重复引用,也可以对本身是引用的变量名再次进行引用,例如:
int main()
{
int a以上是关于硬核两万字文章带你C++入门的主要内容,如果未能解决你的问题,请参考以下文章
一篇博文:带你TypeScript入门,两万字肝爆,建议收藏!