一.什么是C++
我们先来谈谈C语言的面向过程思想吧,顾名思义,面向过程是一个以过程为主体执行的过程,就比如说定义一个结构体存储一个人物信息,然后C语言中会有许多过程函数,就是这个人需要执行的操作,动作,然后将人的结构体数据传入这些过程函数中,等于说是过程为主体,每个步骤接替实现,对象数据只提供信息而已。
C++是一门面向对象的语言,面向过程我们已经有了初步认知,是以过程为实现主体,那以对象为主体是什么情况呢。就是相当于将对象数据,以及对象要进行的操作都封装到对象内,这里就涉及到即将接触的类,比如一个学生类,我们类中可以声明学生信息,也可以声明实现学生的操作以及行为,可以和别的类之间实现交互,就相当于把封装函数与数据属性封装在一起(这就是C++三大特性之封装)。当然还有继承与多态两种特性,后文设计--
二.命名空间 namespace
C++中引入了关键字namespace,是因为c语言当中,比如说当你包含了stdlib.h库,其中会有rand函数,所以你只要int rand一个变量,就会有重定义错误。C++解决了这种风险。
namespace 的存在就相当于声明了一个命名空间,namespace后跟命名空间的名字,然后将该命名空间的成员(变量,函数,类型)写入{}中。实现与别的命名空间的隔离。也就是域的隔离原理。
那么什么是域呢,C++中域有函数局部域,全局域,命名空间域,类域。这些域是编译时,查找变量/函数/类型的一个查询路径,域隔离后,因为查找路径不同,自然互不干扰。
1.定义一个基本命名空间
cpp
//定义一个名为yy的命名空间
namespace yy
{
//定义普通变量 ,也可以定义函数 ,结构体
int n;
int add(int x,int y)
{
return x + y;
}
struct node
{
struct node * next;
int val;
}
}
//访问方式
yy::n = 10;
std::cout << yy::n << std::endl;
// 调用函数
int result = yy::add(5, 3);
std::cout << result << std::endl;
// 使用结构体
yy::node myNode;
myNode.val = 100;
2.命名空间可以嵌套
cpp
namespace first
{
namespace second
{
int a;
}
namespace third
{
int b;
}
}
//调用
namespace first :: second :: a;
但这些域作用限定符 :: 会不会显得有些冗余呢,所以就有了using namespace帮助我们展开作用域,这样直接调用即可
注意!! 弊端同样明显,失去了命名空间存在的意义,对命名的变量,函数等的命名保护就失去了作用,所以一般尽量减少使用。常见的使用是在 std库,因为C++的标准输入输出等都需要调用这个命名空间
cpp
using namespace std; //常见库展开
实际项⽬开发中不建议using namespace std。
3.指定命名空间的使用
cpp
// using将命名空间中某个成员展开
using N::b;
int main()
{
printf("%d\n", N::a);
printf("%d\n", b);
return 0;
}
同一个工程中允许存在多个相同名称的命名空间,编译器最后会将其成员合成在同一个命名空间中
所以我们不能在相同名称的命名空间中定义两个相同名称的成员。
注意:一个命名空间就定义了一个新的作用域,命名空间中所有内容都局限于该命名空间中。
那么说说域的调用流程:
域是与编译有关,在编译时候。默认会从全局局部找,但有了域限定,就会找域中的变量
三. 输入&输出 cin cout
需要包含iostream库
iostream:标准输入输出流库
为什么叫做输入输出流呢,相对C语言的强类型性质,C语言中需要类型限制,比如%d等等,数据存储是离散的,比如int要4个字节,字符要1个,那么就算秉着内存对其,也会不连续。
但C++不同,他会把不同的数据类型都转换成字符,都变成字符意味着数据是连续的,就像水流一样,不需要关心数据流动多少,都会自动处理。
举例:像是在A地和B地之间连接了一根水管 。数据就像水一样,在这根管道中连续不断地流动。你可以从源头(键盘、文件)让数据"流"入程序,也可以让数据从程序"流"向目的地(屏幕、文件)。
C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使⽤⽅式去⽤他们。
1.std :: cin 与 std :: cout
分别是istream 和ostream类的对象,面向窄字符的标准输入输出流
2.std :: endl
相当于putchar一个换行符,刷新缓冲区,简单理解成换行,但其实很复杂,后期会涉及
3. >> << 流提取与流插入运算符
cpp
int a = 1;
double b = 1.0;
char c = 'c';
cin >> a >> b >> c ; //相当于scanf a,b,c但是少了类型限制符,会自动识别
cout << a << b << c << endl; //相当于printf
这里为什么会有自动识别类型的功能,主要就是之后要就讲的重载,重载了cin运算符,然后使得cin可以识别各种类型的存储方式以及特性,后再以字符流形式代替各种类型存储
cin是将 输入的字符转换识别成了类型,后砖换成二进制存入内存,cout是将二进制类型转换成字符输出到屏幕
cin / cout由于底层还需要支持printf以及scanf,所以性能上不如他俩,在一些算法竞赛平台可能会有性能问题,所以加入以下三行代码可以解决cin /cout的性能问题

四.缺省参数
缺省参数本身是再声明或定义时候为函数的参数指定的一个默认值。如果调用函数时没有指定实参则采用形参的缺省值。 ---- 缺省参数分为全缺省与半缺省参数
全缺省,顾名思义,全部形参都给缺省值。
半缺省,部分形参给缺省值。注意:半缺省参数必须从右往左依次缺省参数,不能跳跃缺省
注意:缺省参数不能在声明定义中同时出现,规定习惯是在函数声明时候给缺省值。
cpp
#include<string>
#include<iostream>
using namespace std;
void fun1(int a = 0) //给函数实参默认缺省值
{
cout << a << endl;
}
int main()
{
fun1(); //不传则调用缺省值
fun1(199);//传则调用实参
return 0;
}
cpp
#include<string>
#include<iostream>
using namespace std;
void Func2(int a = 10, int b = 20,int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
void Func3(int a, int b = 10, int c = 20) //半缺省 要求只能从右往左,不能跳过
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func2();
Func2(1);
Func2(1, 2);
Func2(1, 2, 3);
Func3(100);
Func3(100, 200);
Func3(100, 200, 300);
}
cpp
// Stack.h
#include <iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a; //数组指针存储栈元素
int top;
int capacity;
}ST;
void STInit(ST* ps, int n = 4); //缺省n为4 (容量)
// Stack.cpp
#include"Stack.h"
// 缺省参数不能声明和定义同时给
void STInit(ST* ps, int n) //声明已经写了缺省,定义不可再次出现缺省
{
assert(ps && n > 0);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
// test.cpp
#include"Stack.h"
int main()
{
ST s1;
STInit(&s1);
// 确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容
ST s2;
STInit(&s2, 1000);
return 0;
}
缺省参数,形象的来说就是个"备胎",没有实参才会使用,有实参了则会抛下缺省参数。
五.函数重载
C++支持在同一作用域 出现同名函数,要求这些同名函数形参不同,这里的不同指的是形参个数,形参类型,形参顺序。这样C++函数调⽤就表现出了多态⾏为,使⽤更灵活。
cpp
#include<string>
#include<iostream>
using namespace std;
//参数个数不同
void fun1()
{
cout << "fun1" << endl;
}
void fun1(int a)
{
cout << "fun1()" << endl;
}
//参数类型不同
int add(int a, int b)
{
return a + b;
}
double add(double a, double b)
{
return a + b;
}
//参数顺序不同
void fun2(int a,char b)
{
cout << a << b << endl;
}
void fun2(char a, int b)
{
cout << a << b << endl;
}
注意:

因为要是调用f1() ,第一个也可以匹配。第二个也有缺省也可以匹配,参数默认为缺省
六.引用
C中我们会习惯去使用指针来做到形参改变实参,在C++中我们新引申出一套改变变量真实值的方法,就是引用。
引用实质就是给原有变量的内存空间找到了一个新的id,也就是取别名,在语法上,不会给引用变量开辟新的空间,但是实际上引用底层还是常量指针保存了,但我们不去考虑这种情况,以语法角度为准。
引用格式: 类型& 引用别名 = 引用对象;
cpp
int& b = a;
//b是a的别名
int& c = b;
//也可以给别名b取别名c
++c; //这里对c的改变也会改变a ,b
//输出这三个结果一致
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
就相当于是三个id ,指向一个空间,通过这三个都可以直接修改到这个内存空间的数值
1.引用特性
- 引用在定义时候必须初始化
- 一个变量可以有多次引用
- 引用不能像指针一样更改指向,一旦引用一个实体,就无法引用其他实体
cpp
int& ra; //未初始化,报错
int& ra = a;//正确
2.引用使用
-
引用传参和指针传参类似
cppvoid swap(int& rx, int& ry) { int temp = rx; rx = ry; ry = temp; } int main() { int x = 3, y = 4; swap(x, y); //对比参数,将实参x,y砖换为rx,ry return 0; }
引⽤在实践中主要是于引⽤传参和引⽤做返回值中减少拷⻉提⾼效率和改变引⽤对象时同时改变被引⽤对象。(引用做返回值,后期讲解)
cpp//出了作用域还有生命周期的可以引用 int& STTop(ST& rs) { assert(rs.top > 0); return rs.a[rs.top-1]; } STTop(st1) = 3; //出了作用域就没有生命周期的,再引用就相当于返回野指针了 int& func() { int a = 0; return a; }
-
引用和指针是相辅相成的,引用无法和指针一样更改指向,但指针也无法同引用一样,安全效率高。
3.引用和指针优缺点对比
引用优点:
- 引用声明必须初始化,更安全,不会出现空引用;指针可以不初始化,可能出现空指针。
- 引用语法更加简单直观,调用也只需要传变量,不需要传递地址;
使用场景:
①函数参数,主要是避免拷贝
②运算符重载 以及 范围for(后期会解释)
指针优点:
- 指针更加灵活,可以更改指向;
- 指针可以指向NULL./nullptr。(表示不指向任何对象,但引用不能空引用)
- 指针可以进行算术运算
- 指针可以多级指针实现更多操作
使用场景:
①要求指向null等,必须使用
②动态数据结构中,需要节点指向,必须利用指针指向别的节点
③需要指向不同对象
4.指针与引用
cpp
指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
这样就不需要⽤⼆级指针了,相对⽽⾔简化了程序
void ListPushBack(LTNode** phead, int x)
void ListPushBack(LTNode*& phead, int x)
七.const引用
1.引用const对象,需要const引用
cpp
const int a = 2;
const int& ra = a;
2.给常量引用
cpp
int b = 10;
const int& rb = b;
因为b是纯右值,更改无意义,还会影响系统编译速率,一块无意义的值系统还需要给他分配空间。浪费性能,因为系统默认会直接优化右值,但加了const系统就知道不会修改了,就可以放心分配给右值空间,保证下来的顺利编译。
3.临时对象
所谓临时对象就是编译器需要⼀个空间暂存表达式的求值结果时临时创建的⼀个未命名的对象, C++中把这个未命名对象叫做临时对象。
当然临时对象也需要分配临时空间,但只读,所以印证常饮用创建临时变量的特性。引用存储类型转换或者表达式,纯右值时候需要!
指针与引用的关系:
二者相辅相成,不可替代
1.语法层面:指针存储变量地址,要开空间;引用取别名,无需新开空间
2.指针建议初始化,但语法上不是必须的;引用必须初始化
3.引用无法在初始化引用一个对象后引用第二个对象;指针可以不断更改。
4.引用可以直接访问对象;指针需要解引用。
5.引用的sizeof时引用类型大小;指针取决于平台32位或64位也就是地址空间所占字节数
6.引用很少空引用,安全;指针会经常空指针野指针
#修改引用的特殊情况,改了引用原来地址指针也能赋新值
cpp
const int num = 100; // 行 1
int* ptr = (int*)# // 行 2
*ptr = 123456; // 行 3
理解为就是两个符号表,一个符号名为num一个*ptr,
但是对应的地址空间一致,num没有改所以还是100,*ptr改了
八.inline 关键字
用inline修饰的函数叫内联函数,编译器会展开内联函数,这样子调用函数就不需要建立栈帧,增加效率(在不影响效率的情况,比如展开过于冗余复杂则编译器会选择不展开)
inline适⽤于频繁 调⽤的短⼩函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
1.C语⾔实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不⽅便调 试,C++设计了inline⽬的就是替代C的宏函数。
cpp
// 正确的宏实现
#define ADD(a, b) ((a) + (b))
加上分号在宏替换的时候可能会出错
括号是为了控制优先级,保证运算正常执行
2.vs编译器 debug版本下⾯默认是不展开inline的,这样⽅便调试
3.inline不建议声明和定义分离到两个⽂件,分离会导致链接错误。因为inline被展开,就没有函数地址,链接时会出现报错。
注意:内联函数的声明和定义都建议写在头文件
要是在类中定义,那么默认也会是内联函数,但是否展开还是看编译器。
九.nullptr (C++11)
C中,NULL是一个空地址,被定义为⽆类型指针(void*)的常量。但在C++中,NULL被认为是宏,值为0。所以使用空值指针时候,NULL明显就不适用了,才引出了nullptr。
nullptr可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被 隐式地转换为指针类型,⽽不能被转换为整数类型。