C++系列-----第一关:C++入门
前言
开启c++大门的时候,这里还是建议要先学习c语言,再去学习c++(纯纯建议).
1、C++关键字
关键字这里不进行详细的介绍,之后会进行逐步的补充.关键字是需要自己去一步一步熟悉的,灌汤的方式会消化不良,下面展示一下C++总计的63个关键字

2、命名空间
变量,类和函数的名称都统统在全局作用域里,而全局作用域是程序在识别名称最先搜查的地方.
为了不让命名冲突,namespace就诞生了.
比如说:你调用了张三和李四的头文件,如果没有namespace,那么一旦张三和李四有重命名(比如张三和李四都定义struct treenode),你在定义struct treenode的时候,一旦运行,程序就不知道你这个东西是谁的进而造成不必要的麻烦和冲突,namespace就是在这种情况下出现的.
2.1命名空间的定义
下面就是要介绍如何定义命名空间了,命名空间的话就要用到刚才提到的namespace关键字,后面要跟命名空间的名字,再加个{},在{}里面就是空间的成员
可以理解为,namespace后面要加上老大的名字,括号就是老大的地盘,后面调用的时候就是可以理解作报老大的名字
一般做项目的时候要把项目名称当作命名空间名
但是自己用的话,自己名字简称就行,比如张三:zs
1.正常的命名空间定义
在命名空间中可以定义变量,函数,类型
namespace zs
{
int a = 0;
struct TreeNode
{
struct Node* next;
int Element;
}
}
2.命名空间可以嵌套
//在上面例子基础上嵌套:
namespace zs
{
int a = 0;
struct TreeNode
{
struct Node* next;
int Element;
}
namespace ls
{
int b = 0;
}
}
3.一个工程里可以存在多个相同名称的命名空间,编译器会把他们统一起来,毕竟他们都属于同一个老大
//大哥都是zs(一个工程中),这样就会被合并统一查询
namespace zs
{
int c = 0;
}
注意:一对一的关系,一个命名空间对应一个新的作用域,空间里的内容都被限制在命名空间里
2.2命名空间的使用
那么命名空间选中的成员该如何使用呢?
在成员前面加上老大的名字就行,知道这个空间属于谁
命名空间的使用有3种方式:
(1)加命名空间名称及作用域限定符
比如:
//定义一个命名空间:zs
namespace zs
{
int a = 0;
}
现在你要使用命名空间:zs下的变量a
那么就要这样用zs::a
//打印的话就是这样
printf("%d",zs::a);
zs::a这里补充一下,如果::前面没有东西或者是个空格,那么就表示在全局作用域里寻找,zs::a就是在zs这个作用里找到a这个名称
(2) using将命名空间中某个成员引入
//拿(1)的例子来说,我们还可以这样用
using zs::a
int main()
{
printf("%d",a);
}
(3)使用using namespace 命名空间名称引入
using namespace zs
int main()
{
printf("%d",a);
}
对于(3)这种用法就是把命名空间zs放到全局作用域去了,公司里一般不会这样用
,(2)的话就是对于自己经常要用的单独进行命名空间引入,(1)的话用起来麻烦,但是对于一些不常用的话,觉得没必要用(2)声明就用(1)这种方式
3.C++输入与输出
类似于c语言的scanf和printf,c嘎嘎也是有自己输入和输出滴
#include <iostream>//头文件
//这里using的作用前面提过,std就是c++标准库的命名空间名,也就是那个老大
using namespace std;
int main()
{
//这是输出hello,"endl"相当于c语言的"\n"表示换行的意思
cout<<"hello"<<endl;
return 0;
}
关于c++的输入输出来说,他是可以自己识别类型的,不用像scanf和printf那样还要告诉类型,当然如果习惯c语言的话,还是可以用scanf和printf的,c++可以兼容c语言的很大一部分
1.cout是标准输出对象,cin是标准输入对象
2.cout和cin是全局的流文件
3.<<是流插入运算符,>>是流提取运算符(按照平常阅读习惯从左向右看,可以把<<想象张嘴成吐出的感觉,>>可以想象成箭头,有一种放入内容的感觉,方便记忆,提一嘴)
4.cout是ostream的对象,cin是istream的对象,后面IO流用法及原理还要说
注意:由于命名空间以及与C头文件区分等问题,这里推荐加上std的方式,
还有就是cout和cin还有更复杂的用法,比如浮点数精度的问题,但是不常用,不展开讲述,可以用printf来解决一些精度问题
对于std命名空间的使用习惯:
上面提过命名空间的使用,所以在使用using namespace std时,会把标准库暴露出来,这样可能会在做项目时发生冲突,平时用倒是问题不是很大,做项目时注意要用指定命名空间+常用库对象/类型等方式,比如using std::out
4.缺省参数
4.1缺省参数概念
缺省参数是声明或定义函数时为函数的参数指定一个缺省值。在调用该函数时,如果没有指定实
参则采用该形参的缺省值,否则使用指定的实参。
举个例子:
#include <iostream>
using namespace std;
void a(int x = 0)
{
cout<<x<<endl;
}
int main()
{
a();//这里调用函数没有传参,按照默认参数值来输出的(x=0)
a(1);//在有参数传入的时候,会将默认值覆盖(x=1)
return 0;
}
4.2缺省参数分类
(1)全缺省参数
可以理解为函数的参数都有默认值
举个栗子:
void a(int x = 0, int y = 0)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
}
上面x,y就是都有自己的初始值
(2)半缺省参数
不全就是半缺省参数
void a(int x, int y = 0)
{
cout<<"a = "<<a<<endl;
cout<<"b = "<<b<<endl;
}
这里没给x赋予初始值,就不全,所以是半缺省参数
注意:
1.半缺省参数需要从右向左给出,不能间隔,这里是规定,间隔的话调一下顺序就可以符合从右向左给出的规定了
2.缺省参数不能在函数声明和定义中同时出现
//如果声明和定义同时出现默认值,编译器会无法辨别要用那个缺省值
void a(int x = 10);//定义
void a(int x = 20)//声明
{}
3.缺省值必须是常量或者是全局变量
int x = 100;//举个全局变量的例子,这个代码输出就是a = 100 b = 20 c = 30
//这里a就默认值x(即100)
void A(int a = x, int b = 20, int c = 30)
{
cout << "a = " << a << " ";
cout << "b = " << b << " ";
cout << "c = " << c << " ";
}
int main()
{
A();
return 0;
}
还有就是在函数传参的时候,是从左向右赋值,因为你从右向左给的默认值(前文提过),这样就好理解了,举个栗子:
//还是刚才的函数,在调用A函数的时候,也是不能跳着给值(前文缺省参数也是不能间隔赋值),像A(1, ,3)这样是不行的
A(1);//a=1,b=20,c=30
A(1,2);//a=1,b=2,c=30
A(1,2,3);//a=1,b=2,c=3
5.函数重载
5.1函数重载
函数重载:C++允许在同一作用域中声明几个功能类似的同名函数,这
些同名函数的形参列表(参数个数 或 类型 或 类型顺序)不同
举个栗子(头文件大家自己记得写,在这里提醒一下初学者):
//参数个数不同的情况:
int a(int b, int c)//为了后面好说明,这就是第一个a函数
{
return b + c;
}
int a(int b, int c, int d)//第二个a函数
{
return b + c + d;
}
//上面出现了两个a的函数
int main()
{
cout << a(1, 2, 3) << endl;//调用第二个a函数
cout << a(1, 2) << endl;//调用第一个a函数
return 0;
}
//类型不同的情况:
double a(double b, double c)
{
return b + c;
}
int a(int b, int c)
{
return b + c;
}
int main()
{
cout << a(1.1, 2.2) << endl;//c嘎嘎是可以自己识别类型的,所以这里调用第一个函数
cout << a(1, 2) << endl;//这里调用第二个函数
return 0;
}
//类型顺序不同的情况:
void a(double b, int c)
{
cout << b << " " << c << endl;
}
void a(int b, double c)
{
cout << b << " " << c << endl;
}
int main()
{
a(1.2, 2);//调用第一个函数
a(3, 3.2);//调用第二个函数
return 0;
}
5.2函数重载的原理:名字修饰
程序运行的过程:预处理,编译,汇编,链接
链接时,该如何寻找函数呢,尤其是在函数重载的情况下,这里就要对函数名称进行修饰来区分,windows里vs的函数名修饰很复杂,linux下的g++修饰规则简单易懂,比如对于上面说的a函数在类型顺序不同的情况下,double简化为d,int简化为i,然后就是前后顺序不同来区分类型顺序不同.
这一块大家简单理解一下即可,好奇的同学可以去搜索一下相关资料.
而c语言不能支持函数重载,也是因为对同名函数无法具体化区分,c++则就是根据函数重载修饰规则来区分
注意:如果两个函数函数名和参数一致,但是返回值不同,比如int a(int x)和void a(int x)这两个函数名一致,参数相同,对于这种情况是无法构成重载,因为调用时编译器没办法识别.有宝子就要说了,在函数名进行修饰的时候加上前面的返回值不就行嘛,举个栗子:
void a(int x)
{
cout << x << endl;
}
int a(int x)
{
cout << x << endl;
return 0;
}
int main()
{
a(1);
return 0;
}
这里也可以是第二个a函数,因为即便a函数有返回值,但他还是可以不接收,第二个a函数在调用也是可以这么写
即便你在函数名修饰的时候对返回值进行了修饰,但是你这里调用的时候,用编译器的角度来看,没有int接收,而且第二个a函数就是可以用单独a(1);这个语句来调用函数,所以和第一个函数区分不开,所以就无法进行函数重载
6.引用
6.1引用概念
引用是给已存在变量取了一个别名.但底层里讲吧,其实还是用到c语言里的指针,看完大家可以尝试看看别名和指针调用时的汇编,都是一个用法
引用用法:类型&引用变量名(对象名) = 引用实体;
int a = 10;
int& b = a;//可以理解为给a起了个外号叫作b,其实就是b和a都公用一个地址,对b修改也是对a修改
注意:引用类型必须和引用实体是同种类型的
6.2引用特性
- 1.引用在定义时必须初始化
- 2.一个变量可以有多个引用
- 3.引用一旦引用一个实体,再不能引用其他实体
//第一个特性呢就是你定义引用右边必须有实体,不能空着
int a = 10;
//int& b;这样就不行,不能放着不给东西
int& b = a;//必须要这样才可以
int& c = a;
//第二个特性就是你可以给一个变量起很多名字,比如上面这个a,给他起好多名字,比如b,c...建议还是起名字起的有意义
//第三个特性的意思是你给张四起了个外号叫小四,这个小四就只能指向张四这一个实体,如果你还指向别的实体
//系统认为你重定义,张四也不知道你是不是再叫他,所以一个实体有多个外号,一个外号只能对应一个人,像极了数学的函数
6.3常引用
常引用要注意权限问题,只能缩小权限或者平权,不能放大权限,举个栗子:
const int a = 1;
int& b = a;//此处就把a的权限放大了,会报错,因为在const的修饰下,a就是个常量,只能读取,不能修改
//上面说过,取别名本质还是地址嘛,b和a共用一个地址,因为b没有const的修饰,所以b可以去修改数值.进而把a的数值
//篡改了,这里变量b的权限可读可写,而a只可读,给a取b的别名会把a的权限放大
const int& b = a;//这样才能正常引用
int d = 1;
double& e = d;//这里类型不同也会编译报错,这里有宝子就说了,d的权限没有被缩小,为什么不行
//这里就要牵扯到类型转换了,在类型转换过程中,系统会用一个临时变量对d的数值进行类型转换,进而保护d的原值
//这个临时变量的数值再赋值给e,而临时变量在系统中是不可修改的常量,这样看来,其实还是把权限放大了
//但是如果我们const修饰e的话就没事了
const double& e = d;//这样就不会出错了
6.4使用场景
1.做参数
c语言里实现两个变量交换各自值的时候,要用到指针,不然形参是无法改变实参的,这里引用就看起来简洁了
void Swap(int& a, int& b)//这里引用就是给实参起了外号,这样就可以直接影响
//实参了
{
int tem = a;
a = b;
b = tem;
}
2.做返回值
//
int& a()//这里就是给返回值x这个静态变量取别名
{
static int x = 0;//静态变量x不会因为a函数栈帧的销毁而是变量值改变
//栈帧的销毁我后面会出的,先强行理解一下
x++;
return x;
}
int main()
{
cout<<a();//输出1
return 0;
}
//再举个栗子:
int& Add(int a, int b)//引用返回值
{
int c = a + b;
return c;
}
int main()
{
int& ret = Add(1, 2);//ret是c的外号,但c变量会随着函数调用结束,可能会被
//编译器类似流放的感觉,谁的可以用,也可能没用,但也可能编译器直接给了个随机值,所以ret的值不确定
//可能流放了没人管,还是3,也可能编译器强制虽随机值,或者是被其他的程序给拿走赋别的值了
Add(3, 4);
cout << "Add(1, 2) is :" << ret << endl;//刚才又调用Add(3,4)但是伴随着调用结束,还是被
//c的那个位置还是会被流放,虽然函数的空间被流放了,但那块地方还在.
//此时ret的值,或者说是c的值还是随机值,取决于编译器,如果没有编译器没有马上修改的话,
//ret的值就是7,但是要是被修改的话,就充满了随机
return 0;
}
//如果把int& ret改成int ret的话就是单纯给ret赋值了,毕竟这里不是c的引用,后面打印就是3
//如果把Add函数的返回值改成int的话,也会报错,因为这就是上文提到的常量引用的一个错误
//对于常量只能读取,所以要用const修饰达到类似平权的效果
//像这样const int& ret = Add(1,2);
//这样的话ret就是接受了一个常量值3,后面就会打印3了
6.5传值与传引用的比较
上文提过,函数再销毁之后,空间流放,任人宰割,但是我们还有返回值要传,那就要借助一个临时变量来承载这个返回值,传参也是一样都需要拷贝,效率就会因为拷贝的时间而使效率降低,尤其是参数或者返回值类型很大的时候
6.6引用和指针的区别
别名就是一个语法概念,他是和引用实体共用一个空间
引用的底层实现是按照指针方式来实现的
关于指针和指针的不同:
1.引用在概念上是定义一个变量的别名,就是起外号,指针是存储一个变量地址
2.引用定义必须初始化,指针随便
3.引用初始化一个实体后,不能再引用其他的实体,指针随意
4.指针调用要显式解引用,引用则需要编译器自己处理
5.引用更安全,毕竟指针可能有野指针,而引用本身必须初始化而且单一个体,所以相对安全
7.内联函数
7.1概念
以inline修饰的函数叫做内联函数,编译时C++编译器会在调用内联函数的地方展开,没有函数调
用建立栈帧的开销,内联函数提升程序运行的效率。
将一般函数前增加inline关键字改成内联函数,会在编译期间将函数体替换成函数的调用

在这里大家大概的了解一下可执行程序的那种过程,上图第一次调用add是一条运行指令,call就相成调用的意思,跳转到函数,执行add运行指令,第二次调用add也是一条运行指令,然后调用add函数,跳转到add地址,此处的五行运行指令还是之前的那五行,所以一共是7行运行指令
而这个上图中是add函数被inline修饰的情况,第一次add直接就展开了五行运行指令,第二次也是展开了五行运行指令,大家可以理解为每次使用函数都要展开展开那五行运行指令,这样就有10条指令了,不像第一次那样一直在调用同一个地方的五行指令
7.2特性
1.inline是一种以空间换时间的做法,如果编译器将函数当作内联函数处理,在编译阶段,会用函数体替换函数调用,代入上面的栗子就是把add函数体的调用替换成了直接转换成函数体,也就是那五行指令
这里的空间不是内存,是上面栗子中指令,会使原来指令变多,结合上述例子就是7行变成了10行,如果函数体指令更多的话,那差距会更大,所以一般应用规模较小的函数.inline在编译器认为就是一个建议,如果你的建议不合理,函数体太大,还要内联函数,编译器也可不采用inline修饰
2.缺陷:可能会使目标文件变大,优势:少了调用的开销,提高程序运行效率
3.inline不建议声明和定义分离,分离会使链接错误,因为inline被展开,就没有函数地址,链接也找不到
面试方面:
c++定义inline是为了让他代替宏函数
可能面试会问到有关宏的优缺点:
优点:
1.增强代码的复用性
2.提高性能
缺点:
1.不方便调试宏(因为预编译进行替换)
2.导致代码可读性差,可维护性差,容易误用
3.没有类型安全的检查
c++替换宏可以采用:
1.常量定义,换用const,enum
2.短小函数定义,使用内联函数
8.auto关键字
8.1类型别名思考
在声明变量的时候要清楚表达式类型,随着类型复杂性的提高,难度也会提高,所以auto的诞生就很方便的简化代码
8.2 auto的使用
auto必须进行初始化,编译器才可以推导出它的实际类型,在编译期间,编译器会将auto替换成变量实际的类型
1.auto与指针和引用结合
auto声明指针,auto和auto*没有区别,但是声明引用是必须要加&
举个栗子:
int main()
{
int a = 1;
auto b = &a;//b等于a的地址
*b = 10;
cout << a << endl;//a = 10
auto* c = &a;///同样的,c等于a的地址
*c = 20;
cout << a << endl;//a = 20
auto& d = a;//对a取别名(引用)
d = 30;
cout << a << endl;//a = 30
auto e = a;//e代表的类型与a一样是int类型,并赋值a的变量值即30
e = 40;
cout << a << endl;//a没有改变,还是30,所以引用必须要加&,不然会与这种情况混淆
cout << e << endl;//e仅仅是与a的类型相同,并不会改变a
return 0;
}
2.在同一行定义多个变量
要想在同一行定义多个变量就要保持类型一致,不然编译器会报错,因为编译器是只对第一个类型进行识别的,用这个第一个识别的类型定义后面的变量
3.auto不能用的地方
1.不能作为函数参数,返回值可以用
int add(anto a,auto b)//这里不可用
auto add(int a,int b)//此处可行
2.auto不能直接用来声明数组
auto arr[] = {1,2,3};//这里就不行(达美)
9. 基于范围的for循环
9.1 范围for的语法
for(迭代的变量:被迭代的范围)
//举个栗子
int main()
{
int arr[] = { 1,2,3 };
for (auto tem : arr)
{
cout << tem << " ";//依次打印出1 2 3
}//依旧可以用continue结束本次循环,break跳出整个循环
return 0;
}
9.2 范围for的使用条件
1.迭代范围明确
数组的话就是从一个元素到最后一个元素的范围
2.迭代对象要实现++和==的操作.(后面会说,这里提一下)
10.指针空值nullptr
NULL实际是一个宏,被定义时可能是0或者是无类型指针(void*)的常量,这样总会带来一些麻烦
注意:
1.在使用nullptr时不需要引头文件,因为在c++11已经把nullptr作为关键字,表示指针空值
2.在c++11中,sizof(nullptr)和sizeof((void*)0)所占字节数相同
本文是C++入门教程,介绍了C++关键字、命名空间的定义与使用、输入输出、缺省参数、函数重载、引用、内联函数、auto关键字、基于范围的for循环以及指针空值nullptr等内容,还分析了各知识点的原理、特性和使用注意事项。
26万+





