一、初识c++
简要介绍c++
c++是在c语言的基础,容纳了面向对象编程的思想,同时增加了许多有用的库,以及编程范式。在语法上,c++是兼容C语言的,同时对C语言某些设计不合理的地方进行一些优化。
c++关键字
命名空间
namespace
在 c/c++中,变量和函数和类是大量存在的,这些变量,函数和类的名称都存在于全局作用域中,可能会导致很多的冲突。
命名冲突
1.自己写的与库冲突
2.两个人写的之间相互冲突
C语言没有办法解决这样的问题,c++的namespace 命名空间很好的解决了这个问题。
首先我们先看一个样例
我们可以看到,代码显示编译错误。为什么rand没有办法打印呢?
这是因为rand 是一个函数名,存在命名冲突的问题。
如何解决这样的问题呢?我们可以使用命名空间。
命名空间的定义
定义命名空间需要使用namespace关键字,后面加上命名空间的名字。然后再加一对{} 。
在{}内部是命名空间的成员。
例如:
在命名空间中定义多个变量
namespace a
{
int rand;
char a;
double c;
float d;
}
对命名空间的扩展;
命名空间中可以定义变量,函数,自定义类型。
namespace sss
{
int rand ;
int ADD(int a, int b)
{
return a + b;
}
struct st
{
struct st* next;
int val;
};
}
命名空间可以嵌套。
命名空间解决了自己和库,或者自己和别人的命名冲突等问题,但是如果自己和自己发生冲突了怎么办?命名空间可以嵌套。
namespace sss
{
int rand ;
namespace a
{
int rand;
}
namespace b
{
int ADD(int a, int b)
{
return a + b;
}
}
int ADD(int a, int b)
{
return a + b;
}
struct st
{
struct st* next;
int val;
};
}
命名空间的使用
在使用命名空间后,编译器默认访问全局变量,如果想要使用命名空间中的变量 ,可以用以下三种方式进行使用。
1.命名空间名称+作用域限定符
#include<stdio.h>
namespace ss
{
int a = 0;
int b = 0;
int ADD(int x, int y)
{
return x + y;
}
}
int main()
{
printf("%d", ss::a);
return 0;
}
2.使用using将命名空间中的某个成员引入
(指定展开)
每次指定命名空间很不方便,但是直接全部展开又有风险。指定展开可以解决。
3.使用using namespace 命名空间名称引入
(全部展开)
C++的输入和输出
在C语言中 用printf和scanf来实现输出和输入,在c++中,我们使用 cout 和cin 来完成输出和输入
cout :标准输出对象
cin: 标准输入对象
<< 流插入运算符 >>流提取运算符。
在使用时,必须包含头文件以及按照命名空间使用方法使用std
c++的头文件是不包含.h 的。在运用的时候需要注意。
#include<stdio.h>
#include<iostream>
using namespace std;
int main()
{
int a = 0;
cout << "hello" ; //直接用cout进行打印
cout << "hello" << "\n";
cout << "hello" << endl;
cout << a;
return 0;
}
在打印的时候 ,cout后面的<< 是流插入运算符
与之对应的 还有cin 后面的>> 叫做流提取运算符
它们实现类似scanf的功能。
endl (endline) 和/n都表示换行
cout 与printf 不同的是,cout可以自动识别类型,不需要像printf那样 例如打印一个整型还要以%d 的形式打印。
缺省参数
定义:缺省参数是声明或定义函数时,为函数的参数设定一个缺省值。在调用该函数时,没有指定实参则采用该形参的缺省值,否则采用指定的实参。
缺省参数的分类
1.全缺省参数
2.半缺省参数
必须从右往左依次来给出,不可以间隔着给参数
//全缺省
void func1(int a = 1, int b = 2, int c = 3)
{
}
//半缺省
void func2(int a, int b = 6, int c = 7)
{
}
函数重载
是函数的一种特殊情况,c++允许在同一作用域中声明几个功能类似的同名函数。这些同名函数的形参列表不同,常用来处理实现功能类似,数据类型不同的问题。
构成函数重载的要求:
1.函数名相同
2.参数不同(类型不同/个数不同)
3.返回值随意,可同可不同
(函数调用是不看函数返回值的,如果函数重载 与返回值相关,会产生歧义)
**为什么C语言不支持函数重载?
C语言函数调用用的是函数名
但是c++在调用函数时,靠的是通过函数修饰规则组成的每个函数独有的名字找到对应函数,因此可以实现函数重载。
引用
引用的概念
引用不是新定义一个变量,而是给已经存在的变量定义一个别名。
*编译器不会为引用变量开辟新的空间。它和它的引用的变量共用一块空间
比如张三 别名叫二狗,张三是他,二狗也是他。两个名字对应的是一个人。
因而引用实际上是取别名。
就像一个人可以有多个外号,一个变量也可以有多个引用。但是一个引用不可以对应多个变量。
引用的使用方法
类型& 引用变量名 = 引用实体
例如:
这里的c是引用变量
图中看出,二者共用同一块空间
引用的特性
1.引用在定义时必须初始化。
2.一个变量可以有多个引用
3.引用一旦引用一个实体就不可以再引用其他实体。
(一个别名只能对应一个人,一个人可以有多个别名)
引用的价值
1.做参数(核心价值)
我们知道,函数传参的时候,形参是实参的拷贝,所以在对实参的值进行修改的时候,往往需要传地址,但是使用引用就可以避免传址而对实参进行修改。大大提高了效率。
#include<iostream>
#include<stdio.h>
using namespace std;
//引用变量做参数
void Swap(int& a,int &b)
{
int t = a;
a = b;
b = t;
}
//使用指针做参数
void Swap(int* a, int* b)
{
int t = *a;
*a = *b;
*b = t;
}
int main()
{
int a = 10;
int b = 29;
Swap(a, b);
printf("%d %d\n", a, b);
int c = 1;
int d = 0;
Swap(c, d);
printf("%d %d\n", c, d);
return 0;
}
2.做返回值
函数返回值采用传引用返回需要注意以下几点
1.返回值是返回变量的别名,我们知道,在函数中创建的变量申请的空间,出了函数作用域会被销毁,那么变量对应的值,如果被清理后就是随机的,没有被清理就是原值,这个时候采用的传引用返回会导致结果不确定。
所以引用返回适用于出了作用域不会被销毁的变量,例如 静态变量,使用malloc开辟空间的变量等等。
那么引用做返回值有什么好处呢?
1.提高效率
2.引用做返回值可以修改返回对象#
3.引用作参数可以修改形参,从而导致实参的改变
上述的引用都是对变量做的引用,那么对于常量又该如何使用引用呢? 下面引入常引用的概念
图中我们可以看出此种写法是错误的,原因是涉及到了权限的问题。
对于引用,可以平移权限或缩小权限,但是不可以放大权限。图中 a是常量,此时给a起了一个别名b,本来,a是不可以修改的,但是起别名后有想给a放大权限可以修改的意图,这是不允许的。
正确的写法如下:
别名前面也用const进行修饰 这是平移权限
对于一般常量例如数字等等,也可以创建引用变量
那么不同类型变量可以使用引用变量吗?
答案是不可以,类型不同。
但是,为什么使用const修饰就可以了呢?
这里我们需要知道,有double转为int 的强制类型转换,会产生一个中间的临时变量,这个临时变量具有常性,不可修改,使用const后就不会报错了。
引用和指针的区别
我们了解到,引用最核心的价值在于做参数,可以实现通过改变形参而改变实参,指针也可以做到,那么引用和指针到底有什么区别呢?
从概念上:
引用指的是定义一个变量的别名:
指针是储存这个变量的地址。
在使用时:
(1)引用在定义时必须初始化,但是指针没有要求
(2)一个引用只可以对应一个实体,不可以进行修改,但是指针可以对应不同的实体,只需要修改存储的地址就可以修改对应的实体。(这也是在c++中引用无法取代的原因之一)
(3)没有空引用但是有空指针,引用是别名,一旦产生就必须要有一个指向的对象,但是指针变量不需要。
(4)在sizeof中的含义不同,引用的结果为引用类型的大小,但是指针始终是地址空间所占的字节个数。
(5)引用自加,是引用的实体加一,但是指针自加是指针向后偏移一个类型的大小。
(6)有多级指针但是没有多级引用
(7)访问实体的方式不同,指针需要解引用,但是引用是靠编译器完成的。
(8)引用比指针使用起来相对安全。但不是绝对安全(比如传引用返回)
内联函数
我们知道,在C语言中有宏的概念。在预编译阶段,宏名直接替换为宏体,大大提高了代码的复用性,但同时宏也有很多缺点:1.、宏的语法细节多,在写宏时容易出错。2.宏不可以进行调试。3.宏没有类型安全的检查。
为了解决这些问题,c++对此用enum ,const,和inline 来替换宏。其中enum 和const 可以替换宏常量,inline可以替换宏函数,下面让我们对inline来进行详细的分析。
内联函数;
概念:
以inline修饰的函数叫做内联函数,编译时c++编译器会在调用内联函数的地方展开,没有函数调用建立栈帧的开销,可以提升程序运行的效率。
下面是简单地内联函数
inline int func()
{
int a = 2;
return 2;
}
内联函数的特性:
在调用函数的时候,编译器会用函数体来替换函数。
由于内联函数调用的时候不会调用栈帧,可以大大提高效率。
但是内联函数可以任意使用吗?可以将所有函数都换成内联函数吗?
答案是不可以。
因为:内联函数是一种以时间换空间的做法。用函数体来替换函数调用,如果任意使用的话就会让可执行程序变大。所以内敛韩式只适用于频繁调用的小函数(例如十行以下)
当然,就算是大函数使用了inline也没什么问题,因为inline对编译器而言仅仅是一个建议,不同的编译器关于inline的实现机制是不同的。编译器可以忽略这个请求
inline不支持声明和定义分离:
我们一般的函数很常见的声明和定义是分开的,但是inline定义的函数不支持。因为内联函数不会生成地址,没有地址,链接就会找不到,自然也就无法使用。
auto关键字
在定义变量的时候,我们需要判断这个变量是什么类型的。但是,随着程序不断复杂化,变量的类型也逐渐复杂化,这时 我们只需要auto 和typeid 就可以清楚地知道变量类型。
auto 关键字 是一个类型指示符来只是编译器。可以自动推导类型。
typeid 可以用来打印对象类型
使用方法:
auto使用规则
1.auto 可以与指针和引用结合起来用。与指针结合时,auto 和auto*没区别,但是引用 必须写成auto&的形式。
这里注意:关于e e是引用变量,为什么打印出来是double类型呢?
因为e是c的别名,c是double类型,那么e当然也是double类型啦。
2.auto可以在同一行定义多个变量(一行的类型要相同) 编译器实际上是对第一个变量的类型进行推导,然后用推导出来的类型进行定义后面的变量。
3.auto 不可以作为函数的参数
4.auto不可以用来声明数组
基于范围的for循环
在C语言中打印数组,往往是使用for进行循环遍历的打印。但是在c++中,我们有了另一种打印方式
void Test()
{
int arr[] = { 0,1,2,3,4,5,6,7 };
for (int i = 0; i < sizeof(arr) / sizeof(arr[0]); i++)
{
printf("%d", arr[i]);
}
printf("\n");
// 基于范围的for循环
for (auto e : arr)
{
cout << e;
}
}
**需要注意的是 只有打印数组可以这样用,这里的arr是数组。
像这样是不可以使用的,这里的brr是指针
指针空值—nullptr
null 是 0,nullter 是空指针。
因此,在初始化空指针是用nullter