- C++入门知识
- C++关键字
- 命名空间
- C++输入与输出
- 缺省参数
- 函数重载
- 引用
- 引用概念
- 引用特性
- const与引用
- 引用的应用
- 引用与指针的区别
- 引用与指针的汇编代码是一样的
- 内联函数
- 内联函数
- 为什么不用宏
- 适用条件
- 内联函数的特点
- auto关键字
- 基于范围for循环
- 指针空值
C++是一门细节很多的语言,不太好学。我们从开始就要认真地学习它的这些细节,后续的学习才不至于十分吃力懵逼。
C++关键字
C++总共有63个关键字,因为C++本身兼容C语言,所以C++的63个关键字里面包括了C语言的32个关键字。这63个关键字都是啥呢,看下面的图。
这篇文章我们主要讲解入门知识,对于上述每个关键字我们就不具体展开讲了,随着学习的深入我们会逐渐了解这些关键字的。
命名空间我们如果看过c++代码的话,会经常看到下面这段代码
using namespace std
这里的代码是什么意思?我们等会再说。
还有我们在写c语言代码的时候会经常出现命名冲突。例如:
#include
#include
int rand=0;
int main()
{
printf("%d",rand);//这里会报错,因为全局变量会与库函数里面的rand冲突。
return 0;
}
出现了命名冲突就得改名字了,改名字太麻烦了,如果变量和函数经常使用那么改名字将会非常令人头疼。
C++为了解决命名冲突,引入了namespace这个关键字。
#include
#include
namespace mll
{
int rand=0;
}
int main()
{
printf("%d",rand);//现在不会报错了,但是会打印出一个很大的数,这个数字是什么呢?
return 0;
}
这个数字就是rand函数的地址。为什么没报错呢?我们知道在同一个作用域里不能有同名的变量或函数或者类型。这里没报错说明,我们定义的变量rand与rand函数没在同一个作用域,观察代码不难推测rand可能被“关起来了”关到了mll这个空间里。
那么我们该如何把我们定义的rand打印出来呢?
printf("%d",mll::rand);
这样就可以打印出来我们定义的变量rand的内容0.其中::是域作用限定符,它可以告诉编译器打印的rand是mll空间里的那个rand。
尽管这样我们还是无法避免这样一种情况
张三,李四同属ww公司,同时做一个项目,张三负责链表,李四负责队列,于是张三写了这样一段代码
namespace ww
{
struct ListNode
{
int val;
struct ListNode * next;
} ;
}
李四写下了这样一段代码
namespace ww
{
struct ListNode
{
int val;
struct ListNode * next;
struct ListNode * pre;
} ;
}
最后会怎么样呢。
**首先同名命名空间编译时会被合并。**合并的时候该怎么合并呢,有两个结构体类型都叫 ListNode该丢掉那个留下那个呢?
但是在实际中很难避免上述情况,我们该怎么办呢?这就需要用到命名空间的嵌套了。
在C++中命名空间可以嵌套。
如果出现上述情况我们只要在命名空间里再定义一个命名空间就可以了
张三
namespace ww
{
namespace zs
{ struct ListNode
{
int val;
struct ListNode * next;
};
}
}
李四
namespace ww
{
namespace ls
{ struct ListNode
{
int val;
struct ListNode * next;
struct ListNode * pre;
};
}
}
这下我们就可以把这个命名冲突的问题解决的差不多了。但是新的问题又来了,每次使用这些变量,函数,类型的时候都得指定命名空间确实是好麻烦呀,我们可以用什么方法呢?
namespace N
{
int a = 10;
int b = 20;
int Add(int left, int right)
{
return left + right;
}
int Sub(int left, int right)
{
return left - right;
}
}
现在让你打印a该怎么实现呢
printf("%d",N::a);
第一种就是加上a所从属的命名空间域
using namespace N;
printf("%d",a);
第二种是用了 using namespace 命名空间域名 这样就相当于把这个域给拆了如果全局变量里面没有叫a的变量,编译器就可以直接识别到a。
using N::a;
printf("%d",a);
第三种是把a这个变量从命名空间域里释放出来了,如果全局变量里面没有叫a的变量,函数,类型编译器就能准确识别出来a。
这三种方法还是比较推荐第三种,用谁释放谁,既方便又不容易发生命名冲突。
我们回到开头那个代码,std是c++的标准库的命名空间域,我们使用using namespace std就可以在使用std里面的变量函数类型的时候不用再前面加命名空间域了。
C++输入与输出C语言中我们用scanf和printf等函数来实现输入输出的功能,我们会发现用c语言进行输入输出的时候我们总是要进行格式控制。c++在这里进行了改进如果我们用cin(流提取) cout(流插入)这些函数的时候我们不需要进行格式控制。我们先来看一下c++里的输入输出函数。
cin>>a;//输入a
cout<>a>>b>>c
用c++的输入输出可以不指定格式,还可以多组输入输出。
如果非要控制格式我们还是用printf scanf来实现,因为c++的格式输入输出非常复杂。但是cin cout没有printf scanf运行快。它要引入的头文件是iostream。
缺省参数缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该
默认值,否则使用指定的实参。可以类比备胎。
比如:
void TestFunc(int a = 0)
{
cout<
缺省参数又分成下面类型
全缺省参数类
void TestFunc(int a = 10, int b = 20, int c = 30)
{
cout<<"a = "<
半缺省参数类
void TestFunc(int a, int b = 10, int c = 20)
{
cout<<"a = "<
对于半缺省参数类,只能从右向左缺省而且是连续的。传参数的时候只能从左到右传
缺省参数不能和定义
那用这个缺省参数有什么好处呢比如栈的初始化可以把capacity。
引入头文件的时候可以可以不止.h还可以.c文件
声明和定义的时候不能同时出现,声明定义不给是可以的,声明不给定义给是不可以的。
函数重载在c语言中,我们是不允许两个函数同名的,但是实际上有很多函数的功能是相似的,使用同一个函数名是非常方便的。但是要求些同名函数要满足参数个数或参数类型顺序不同。例如
int ADD(int a,int b)//1
{
return a+b;
}
double ADD(int a,double b)//2
{
return a+b;
}
double ADD(int a,int b,int c)//3
{
return a+b+c;
}
double ADD(double a,int b)//4
{
return a+b;
}
函数二三四都是符合c++重载语法的,函数1与函数2的参数类型不同,与函数3的参数个数不同,与函数4的参数类型不同;函数2与函数3的参数个数不同,与函数4好像参数个数相同,参数类型也相同但是参数类型的顺序是不同的,函数2的第一个参数是int,第二个参数是double,函数4跟它正好相反,所以两者还是不一样的;函数3与函数4参数个数不同所以也是可以重名的。
那看一下下面这段代码。
double ADD(int a,int b)//5
{
return a+b;
}
这个函数5可以跟上面四个函数写一起么?我们发现它跟函数1的参数个数,参数类型,参数类型顺序是完全相同的,唯一的区别就是返回类型不同,这样是不可以构成函数重载的,因为这样写,你如果调用函数,那么调用的是哪个函数呢?计算机也不知道你究竟是想返回一个整数,还是浮点数,所以这样写是不对的。
现在我们已经清楚了函数重载使用场景,也基本上会使用函数重载了。但是会使用不代表理解原理。为什么c++可以支持重载,但是c语言不支持重载呢。
我们得先复习一下编译链接的过程。
-
预处理 头文件展开,条件编译,宏替换,注释去掉
-
编译 语法检查,并将源文件翻译成成汇编文件,每个.c文件生成.s文件,.h文件删除。
-
汇编 将汇编文件翻译成二进制文件,生成.obj文件
-
链接 找调用函数的地址 链接静态库,动态库,链接对应上,合并到一块。
在编译后除了生成的.o文件里面是有符号表的。
标号表里面是啥呢?
目标文件中声明的函数与全局变量。//表示我需要的函数与全局变量。
目标文件中定义的函数与全局变量。//表示我能提供给其他文件使用的函数与全局变量。
看下面这段代码:
在c语言中
符号表
在c++中
编译后生成的符号表如下
发现了没有,在c++代码编译后符号表里两个函数的名字并不一样,第一个函数叫做_Z1fdi第二个函数叫做_Z1fid所以说在c++中函数重载可以实现,编译后重载函数的名字就不一样了。而c语言代码编译后的符号表里函数的名字不变。我们再来看一下c++编译后的名字的变化规则。_Z每个函数都有,1应该是函数名的字符个数,f是函数名,d是double首字母缩写,i是int首字母缩写。我们猜测名字变化规则是:
_Z+函数名字符个数+函数名+第一个参数类型首字母+第二个参数类型首字母+…
下面我们来验证一下:
符号表里的名字
我们的猜想是对的。
最后链接的时候,符号表汇总,我们根据符号表去找调用函数的地址,然后链接对应上。
现在我们讲一下c与c++代码如何互相调用呢?
c++调用c
首先将我们写的c语言代码设置成静态库,这样我们才能链接上c代码
然后在c++项目中打开项目属性做下面 *** 作
这样就可以了。
我们按下ctrl+F5,发现还是错误的,该怎么办呢?
其实我们需要引入的头文件加上extern ‘’C”即可。
extern "C"
{
#include"../Queue.lib/Queue.h"
}
这样编译器在编译的时候就会按照c的方式去给头文件中内容进行编译,符号表里的名字就会和c语言编译的符号表里名字一样,函数名就能对上了,就能找到地址了,也就可以链接上了。
c代码调用c++
还是先把环境调好,把c++项目的项目属性改成静态库,然后把.lib的路径拷到附加库目录,然后在附加依赖项前面加上.lib文件名和;仿照上面我们直接在包含的头文件加上一个extern ”C++“,然而C是没有这种语法的。我们该怎么办呢?
其实我们只能去改c++代码。因为c语言相比c++是缺少很多语法的。
我们可以采用条件编译的方法。![
引用 引用概念引用概念:引用不是新定义的一个变量,而是给已经在内存存在的变量取的一个别名,编译器不会为引用变量单独开辟内存空间的,它和它引用的变量共用一块内存空间。比如李逵,它除了这个名字,他还叫铁牛,也叫黑旋风。但是这三个名字指的都是一个人。
那么引用该怎么使用呢?
int a=1;
int& b=a;//相当于我们给变量a起了另一个名字b
我们想要给a起一个别名b,那么就在b前面加上类型& 就可以创建一个引用。
引用特性引用在定义的时候必须初识化
int& a;//这种写法是错误的。起外号最起码有个对象吧
一个变量可以有多个引用
int a=2;
int& b=a;
int& c=a;
int& d=b;
引用一旦引用一个实体,不能再引用其他实体。
int a=2;
int b=3;
int& c=a;
c=b;//这种写法也是错误的c已经是a的引用了,不能再引用其他实体
const与引用
我们先来看这样一段代码
const int a=12;
int& b=a;//这段代码编译器会报错为什么呢?
我们知道a是一个常变量,只能读不能写,但是b是一个可读可写的引用,这样看b相对于a权限放大。所以编译器会报错,实际上在c++语法种引用时,创建的引用对于原引用变量,权限只能缩小,不能增大。
int a=10;
const int& b=a;//b相对于a权限缩小。
int& c=a;//c相对于a权限不变。
const int d=14;
const int& e=d;//e相对于d权限不变
int &f=e;//f相对于e权限放大,编译器会报错。
有了const我们除了给变量起别名,还可以给常量起别名。
int &a=10;//编译器会报错。
const int& b=10//编译器没有报错 常量是只读,b也是只读的没有放大权限故可以通过编译。
我们还能玩一个更绝的。
double a=3.14;
int& b=a;//报错了这容易理解,a和b类型不同
const int& c=a;//没报错因为c引用的是一个临时变量,临时变量具有常属性看,c前面加了const相对于临时变量就没有放大权限。
引用的应用
引用可以做函数参数
void Swap(int& a,int& b)
{
c=a;
a=b;
b=c;
}
这样写是不是很爽,它既起到了指针的效果,又让我们不用来回解引用了。引用相比于传值调用效率也是非常的高,因为传值调用的时候要把实参进行临时拷贝,这非常地消耗时间。
引用可以做返回值
int ADD(int a,int b)
{
int sum=a+b
return sum;
}
int main()
{
int count=ADD(2,3);
return 0;
}
我们知道出了函数栈帧sum会被销毁,但是为什么count还是变成了5,我们猜测创建了临时变量,sum会先拷贝到临时变量里面,最后临时变量里面的值再赋值给count。我们来证明一下
int ADD(int a,int b)
{
int sum=a+b
return sum;
}
int main()
{
int& count=ADD(2,3);//这里会报错
const int&num=ADD(2,3);//这里不会报错
return 0;
}
这样就证明了以上猜想。如果我们用引用作为&函数返回值会怎样呢?
int& ADD(int a,int b)
{
int sum=a+b
return sum;
}
int main()
{
int& count=ADD(2,3);//这里不会报错了
const int&num=ADD(2,3);//这里也不会报错
return 0;
}
而且我们发现sum和count的地址还是一摸一样的,这就说明如果用引用作为函数的返回值,那么就不会创建临时变量了。但是我们会发现这样是有问题的,因为出了函数栈帧sum已经被销毁了,我们再去访问就出现了了野指针访问的问题。
引用与指针的区别- 引用在定义时必须初始化,指针没有要求
- 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型
实体 - 没有NULL引用,但有NULL指针
- 在sizeof中含义不同:引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占
4个字节) - 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
- 有多级指针,但是没有多级引用
- 访问实体方式不同,指针需要显式解引用,引用编译器自己处理
- 引用比指针使用起来相对更安全
int a=10;
int &b=a;
b=20;
int* p=&a;
*p=20;
内联函数
内联函数
内联函数:用inline修饰的函数叫做内联函数,编译时c++编译器会在调用内联函数的地方展开,没有函数压栈的开销,可以提高程序运行的效率。
为什么不用宏宏不方便调试。预处理阶段进行了替换。
可读性差,可维护性差,容易误用
没有类型安全检查。
适用条件- 函数短小。
- 函数高频使用。
#include
using namespace std;
inline int ADD(int x,int y)
{
int z=x+y;
return x+y;
}
int main()
{
ADD(1,2);
ADD(1,2);
ADD(1,2);
ADD(1,2);
ADD(1,2);
ADD(1,2);
ADD(1,2);
ADD(1,2);
ADD(1,2);
ADD(1,2);
ADD(1,2);
return 0;
}
内联函数的特点
inline是一种以空间换时间的做法,省去调用函数额开销。所以代码很长或者有循环/递归的函数不适宜
使用作为内联函数。
inline对于编译器而言只是一个建议,编译器会自动优化,如果定义为inline的函数体内有循环/递归等
等,编译器优化时会忽略掉内联。
inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会
找不到。
使用c语言编程的时候我们在创建变量的时候需要指明数据类型,我们在c++创建变量或引用的时候可以使用auto,这样就可以不指定类型,不指定类型是不是变量就没有变量类型了,不是的它可以根据赋值来判定类型。
auto x=10;//把10赋给x,可以推测x是整型
typeid().name()可以用来得到变量类型的字符串
auto* a=&b;//a只能是指针类型的变量
auto& x=b;//x是一个变量的引用。
aoto不能连续定义
auto a=10,b=8.132;//这种写法是错误的。
auto不能做参数和返回值
void Fun(auto a)//这种写法会让使用者很困惑所以c++语法里面就没有这种
{
;
}
auto Fun(int b)
{
return b;
}
auto不能声明数组
int main()
{
auto a={1,2,3};//万一你在数组里面放了很多类型的数字甚至还有字符串怎么办
}
使用auto的意义是什么呢?就是可以让我们写代码的时候可以偷懒,比如我们在定义一个类型很长的变量时,可以让编译器自动推导。
基于范围for循环废话少说我们直接上一段代码。
int array[10]={1,2,3,4,5,6,7,8,9,10};
for(auto e:array)//相当于我们对数组自动遍历,遍历到一个元素,就创建一个临时变量e,每次把数组中的元素赋 //给e。
{
cout<
打印的结果时1,2,3,4,5,6,7,8,9,10。
不需要我们再像c语言那样遍历数组打印了。
指针空值c语言中指针的空值时NULL。c++中除了NULL还有一个关键字可以代表空值,nullptr,但是NULL实际上宏定义的,实际上NULL就是0,但是指针空值不因该和0是完全等价的所以我们尽量使用nullptr。
希望大家有所收获。
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)