C++是兼容C语言的,因此C语言的所有语法在C++中都是可以使用的
C语言是存在不少缺陷的,因此C++就对很多缺陷做了改进,产生了一些新的语法和机制
目录
一、命名空间
C语言缺陷:命名冲突
命名空间
展开命名空间
全部展开
部分展开
命名空间的嵌套
命名空间的合并
C++第一个程序,向世界说“你好”
二、缺省参数
全缺省
半缺省
缺省参数应用
三、函数重载
四、引用
引用特点
引用实际应用
常引用
五、内联函数
六、auto关键字
七、基于范围的for循环
八、指针空值nullptr(C++11)
一、命名空间
C语言缺陷:命名冲突
rand本是库函数,但是我们不知道,定义了rand变量,导致了命名冲突
命名空间
命名空间本质就是划分界限,命名空间内部的成员外部是不能随意访问的
命名空间 使用格式:
关键字namespace + 命名空间名字(随便起)
{
//具体成员
}
#include
#include
namespace dck //定义一个叫dck的命名空间
{
int rand = 0;
}
int main()
{
printf("%p\n",rand);
}
那如果想访问命名空间内部的成员呢? --- 使用域作用限定符 ::
使用方法:命名空间名字::成员
#include
#include
namespace dck //定义一个叫dck的命名空间
{
int rand = 0;
}
int main()
{
printf("%d\n", dck::rand);
}
命名空间中可以定义变量,函数,类(理解为结构体)
#include
namespace dck
{
//定义变量
int rand = 10;
//定义函数
int Add(int left, int right)
{
return left + right;
}
//定义结构体
struct Node
{
struct node* next;
int val;
};
}
int main()
{
printf("%d\n", dck::rand);
printf("%d\n", dck::Add(1, 2));
//使用命名空间中的结构体注意::位置
struct dck::Node node;
}
展开命名空间
全部展开
每次使用命名空间中的成员都要加域作用限定符,未免有些麻烦,我们可以直接展开命名空间(相当于授权访问),这样的话外部就可以直接访问命名空间内部成员了,相当于命名空间失效了
全部展开命名空间方法: using namespace 命名空间名字
#include
namespace dck
{
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct node* next;
int val;
};
}
using namespace dck; //展开命名空间---相当于命名空间失效了
int main()
{
printf("%d\n", rand);
printf("%d\n", Add(1, 2));
struct Node node;
}
部分展开
部分展开指定是授权命名空间中的特定成员,其他成员的依然不能直接访问
部分展开命名空间方法: using 命名空间名字 :: 需要授权的成员
#include
namespace dck
{
int rand = 10;
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct node* next;
int val;
};
}
using dck::Add; //部分展开
int main()
{
printf("%d\n", Add(1, 2)); //只能直接访问Add
//其他成员仍需要域作用限定符::
printf("%d\n", dck::rand);
struct dck::Node node;
}
命名空间的嵌套
#include
namespace dck
{
int rand = 10;
namespace xxx
{
int rand = 20;
}
}
int main()
{
printf("%d\n", dck::rand);
printf("%d\n", dck::xxx::rand); //命名空间的嵌套访问
}
命名空间的合并
Stack.h
#include
using namespace std;
namespace dck
{
void StackInit()
{
cout << "void StackInit()" << endl;
}
}
Queue.h
#include
using namespace std;
namespace dck
{
void QueueInit()
{
cout << "void QueueInit()" << endl;
}
}
Test.cpp
#include"Queue.h"
#include"Stack.h"
int main()
{
dck::QueueInit();
dck::StackInit();
}
同一个工程中允许存在多个相同名称的命名空间,编译器最后会合成同一个命名空间中
Test.cpp中包含了两个头文件之后 两个头文件中的命名空间dck 会进行合并,成员就有两个函数
C++第一个程序,向世界说“你好”
写法一: 全部展开
#include
using namespace std;
int main()
{
cout << "hello world" << endl;
}
1.iostream表示出入输出流,是C++标准头文件
2.cout 是标准输出的意思(console out--- 控制台输出) ,相当于 printf;
endl = end of line,相当于"\n",表示换行
3.cout 和 endl 等库函数都是封装在头文件中的std标准命名空间中的,因此使用时不但需要包含#include
头文件, 还需要展开std命名空间 4. << 是流插入运算符,形象理解, "hello world" 流向了 cout ,也就是输出到了屏幕上
写法二:部分展开
#include
using std::cout;
using std::endl;
int main()
{
cout << "hello world" << endl;
}
写法三:域作用限定符
#include
int main()
{
std::cout << "hello world" << std::endl;
}
拓展:
1.cout 对应于 printf, 同时cin 对应于 scanf
2. >> 流提取操作符
#include
using std::cin;
using std::cout;
using std::endl;
int main()
{
int i = 0;
cin >> i; //键盘输入值存到变量i中
cout << i*2 << endl;
}
3. cin 和 cout 可以自动识别数据类型(相比C语言scanf 和 printf 需要指定格式"%d"等等)
#include
using namespace std;
int main()
{
int i;
double j;
cin >> i >> j; //连续输入
cout << i << " " << j << endl; //连续输出
}
4.C++控制浮点数精度比较麻烦,我们直接采用使用C语言方式即可
#include
using namespace std;
int main()
{
double i = 3.34;
printf("%.1f", i); //小数点后保留一位小数
}
二、缺省参数
#include
using namespace std;
void Fun(int a = 1)
{
cout << a << endl;
}
int main()
{
Fun(2); //2
Fun(); //1
}
函数定义时参数给了默认值就成为了缺省参数, 此时调用函数如果不传值就使用默认值,传了实参就使用实参值~
全缺省
函数的所有形参都是缺省参数
#include
using namespace std;
void Fun(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Fun(1, 2, 3);
Fun();
Fun(1);
Fun(1, 2);
Fun(1, 2, 3);
}
半缺省
函数的部分形参是缺省参数,要求从右往左给缺省值,而且调用函数传参时不能跳跃传参
#include
using namespace std;
void Fun(int a, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Fun(1);
Fun(1, 2);
Fun(1, 2, 3);
Fun(1, ,3); //跳跃传参会报语法错误
}
缺省参数应用
栈的实现---未用缺省参数
#include
#include
#include
typedef struct Stack
{
int* a;
int top;
int capacity;
}ST;
void StackInit(ST* pst)
{
pst->a = (int*)malloc(sizeof(int) * 4);
if (pst->a == NULL)
{
perror("malloc fail\n");
exit(-1);
}
pst->top = 0;
pst->capacity = 4;
}
void StackPush(ST* pst, int x)
{
if (pst->top == pst->capacity)
{
int newCapacity = pst->capacity == 0 ? 4 : pst->capacity * 2;
int* tmp = (int*)realloc(pst->a, sizeof(int) * newCapacity);
if (tmp == NULL)
{
perror("realloc fail\n");
exit(-1);
}
pst->a = tmp;
pst->capacity = newCapacity;
}
pst->a[pst->top++] = x;
}
int STTop(ST* pst)
{
return pst->a[pst->top - 1];
}
void STPop(ST* pst)
{
assert(pst->top > 0);
pst->top--;
}
int main()
{
Stack st;
StackInit(&st);
StackPush(&st, 1);
StackPush(&st, 2);
StackPush(&st, 3);
StackPush(&st, 4);
StackPush(&st, 5);
StackPush(&st, 6);
StackPush(&st, 7);
printf("%d ", STTop(&st));
}
栈的实现---使用缺省参数
为了避免频繁扩容,我们可以在实现初始化函数时使用默认参数,初始化时就把空间开好
#include
#include
#include
typedef struct Stack
{
int* a;
int top;
int capacity;
}ST;
void StackInit(ST* pst, int N = 4)
{
pst->a = (int*)malloc(sizeof(int)*N);
if (pst->a == NULL)
{
perror("malloc fail\n");
exit(-1);
}
pst->top = 0;
pst->capacity = N;
}
void StackPush(ST* pst, int x)
{
pst->a[pst->top++] = x;
}
int STTop(ST* pst)
{
return pst->a[pst->top - 1];
}
void STPop(ST* pst)
{
assert(pst->top > 0);
pst->top--;
}
int main()
{
//初始化开辟1000个int大小空间
Stack st1;
StackInit(&st1, 1000);
for (int i = 0;i < 1000;i++)
{
StackPush(&st1, i);
}
for (int i = 0;i < 1000;i++)
{
printf("%d ", STTop(&st1));
STPop(&st1);
}
//初始化开辟100个int大小空间
Stack st2;
StackInit(&st2, 100);
for (int i = 0;i < 100;i++)
{
StackPush(&st2, i);
}
for (int i = 0;i < 100;i++)
{
printf("%d ", STTop(&st2));
STPop(&st2);
}
//不知道开辟多大空间
Stack st3;
StackInit(&st3);
StackPush(&st3, 1);
StackPush(&st3, 2);
//···
}
ps:如果含缺省参数的函数在不同文件中分别有定义和声明,规定声明给缺省值,定义不给缺省值
三、函数重载
概念 : 函数名相同,参数不同,则称构成了函数重载
情形一:参数类型不同
#include
using namespace std;
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
int main()
{
cout << Add(1, 2) << endl;
cout << Add(1.1, 2.2) << endl;
}
情形二:参数个数不同
#include
using namespace std;
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(1);
}
情形三:参数类型顺序不同
#include
using namespace std;
void f(int a, char b)
{
cout << "f(int a, char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
int main()
{
f(1, 'w');
f('w', 1);
}
ps:函数仅返回值不同不构成函数重载, 编译器会报错
#include
using namespace std;
//以下两个函数不构成函数重载
int func(int left, int right)
{
return left + right;
}
float func(int left, int right)
{
return (float)(left + right);
}
int main()
{
func(1, 2);
}
案例:判断一下函数是否构成函数重载:
1.不构成函数重载,函数重载要求函数在同一个域中
namespace dck1
{
void fun(int x)
{}
}
namespace dck2
{
void fun(double x)
{}
}
2.构成函数重载,命名空间名字相同会自动合并
namespace dck1
{
void fun(int x)
{}
}
namespace dck1
{
void fun(double x)
{}
}
3.构成函数重载(缺省参数也是参数呀),但是可能会存在调用歧义
void fun(int a)
{
cout << "void fun(int a)" << endl;
}
void fun(int a, int b = 1)
{
cout << "void fun(int a, int b)" << endl;
}
int main()
{
fun(1, 2); //正确
fun(1); //调用歧义,编译器不知道要调用第一个函数还是调用第二个函数
}
四、引用
引用就是给已存在的变量起别名,因此表示的是同一个变量
int main()
{
int a = 1;
//给a起了一个别名,叫做b,专业术语,叫b引用了a
int& b = a; //固定写法:int&是b的类型,称为引用类型
cout << a << endl;
cout << b << endl;
//类似地
int* p = &a; //p类型是int*
int*& pa = p; //pa引用了p,类型是int*&
//a, b共用一块空间
cout << &a << endl;
cout << &b << endl;
//a,b同时改变
a++;
cout << a << endl;
cout << b << endl;
}
引用特点
1.定义时必须初始化
起别名当然得有对象了,不然是给谁起的别名呢???
int main()
{
int& b; //未初始化(×)
}
2.一个变量可以有多个别名(合情合理)
int main()
{
int a = 1;
//a的别名有b,c,d
int& b = a;
int& c = a;
int& d = a;
}
3.引用一旦引用一个实体,再不能引用其他实体
int main()
{
int a = 1;
int& b = a; // b引用了a
cout << &b << endl; //0000008CA436F7B4
int c = 2;
cout << &c << endl; //0000008CA436F7F4
b = c; //此时b并没有引用a, 只是c赋值给了b
cout << &b << endl; //0000008CA436F7B4
// b地址没变,证明了一旦引用一个实体,不能再引用其他实体
}
引用实际应用
引用所有的应用都是围绕指针展开的,也就是说引用其实是对指针用途的一种替换,而且可能会比指针操作更加简单易懂
1.引用做参数
eg:交换两变量
void Swap(int& p1, int& p2)
{
int tmp = p1;
p1 = p2;
p2 = tmp;
}
int main()
{
int a = 10, b = 20;
cout << "交换前: a =" << a << " " << "b = " << b << endl;
Swap(a, b);
cout << "交换后: a =" << a << " " << "b = " << b << endl;
}
2.引用做返回值
eg:顺序表修改第i个位置的值
C语言指针实现
#include
using namespace std;
typedef struct SeqList
{
int* a;
int size;
int capacity;
}SL;
void SLInit(SL* ps)
{
ps->a = (int*)malloc(sizeof(int) * 4);
ps->size = 0;
ps->capacity = 4;
}
//读取第i个位置的值
int SLAT(SL* ps, int i)
{
return ps->a[i];
}
//修改第i个位置的值
void SLModify(SL* ps, int i, int x)
{
ps->a[i] = x;
}
int main()
{
SL s;
SLInit(&s);
SLAT(&s, 2);
SLModify(&s, 2, 10);
cout << SLAT(&s, 2) << endl;
}
C++引用实现
#include
using namespace std;
typedef struct SeqList
{
int* a;
int size;
int capacity;
}SL;
void SLInit(SL& ps)
{
ps.a = (int*)malloc(sizeof(int) * 4);
ps.size = 0;
ps.capacity = 4;
}
int& SLAT(struct SeqList& ps, int i)
{
return ps.a[i];
}
int main()
{
SL s;
SLInit(s);
SLAT(s, 2) = 10;
cout << SLAT(s, 2) << endl;
}
ps:引用做返回值不能随便用,是有一定条件的
函数调用完之后,函数栈帧就销毁了,这时如果返回的是引用,就可能导致保存了已经销毁的变量的值,出现随机值或者未定义等行为
int& Count()
{
int n = 0;
n++;
return n;
}
int main()
{
int ret = Count(); //返回的是一个已经销毁的变量的引用
//打印结果可能是1,也可能是随机值
cout << ret << endl;
return 0;
}
总结:传引用传参&&传引用返回意义
传引用传参意义(任何时候都可以)
1.提高效率
2.输出型参数(leetcode常见题型,该参数存在的意义不是为了函数内部使用,而是给外界反馈,达到修改外面变量的目的)
传引用返回意义(是有条件的,出函数作用域对象还在适合用传引用返回)
1.提高效率
2.修改返回对象
常引用
规定,在引用过程中(赋值不受权限限制),权限可以缩小,可以平移,但是不能放大
1.权限放大:(×)
int main()
{
//权限的放大(×)
const int a = 0; //a被const修饰了,表明a不能被修改
int& b = a; //(×) b没有被const修饰,表明b可以被修改,此时就叫做权限放大了,会报错
}
2.权限平移:(√)
int main()
{
const int a = 0; //a不能修改
const int& b = a; //b不能修改
}
3.权限缩小:(√)
int main()
{
int a = 0; //a可以修改
const int& b = a; //b不能修改
}
ps:临时变量与权限放大问题
1.数据拷贝是借助临时变量实现的
int main()
{
//赋值不受权限限制
int x = 0;
double y = x;//(√)
//数据的拷贝是借助临时变量实现的,而临时变量具有常属性
int x = 0; //x并没有被cons修饰
double& y = x; //(×)
//x拷贝给y其实是先把x值保存到了一个临时变量里面
//而临时变量具有常属性(被const修饰),因此权限放大了
const double& y = x; //权限平移(√)
}
2. 函数传值返回借助临时变量实现
int func()
{
int a = 0;
return a;
}
int main()
{
int& ret = func(); //(×)
const int& ret = func(); //(√)
}
引用和指针的区别:
1.引用在语法概念是一个别名,没有独立空间,和其引用实体共用一块空间;但在底层实现上,引用是占据空间的, 因为引用是按照指针的方式来实现的
2.引用必须初始化,指针没有要求
3.引用一旦引用了一个实体,就不能引用其他实体,而指针随时可以改变指向
4.有多级指针,却没有多级引用
5.引用比指针使用起来更加安全
6.有空指针,但没有空引用
7.sizeof(引用)计算的是引用实体的大小,sizeof(指针)计算的是地址空间的大小(恒为4/8个字节)
8.访问实体方式不同,指针需要显式解引用,而引用编译器自己会处理
五、内联函数
C语言介绍过宏,而宏是由缺陷的,C++中发明了内联函数可以替代宏,被关键字inline修饰的函数就称为内联函数,内联函数的特点是在调用内联函数的地方函数会直接展开,直接执行函数体的内容,没有函数调用时函数栈帧的开销,提高了程序的运行效率
1.内联函数函数体比较小,且不能包含递归调用,否则直接展开会代码膨胀,导致目标文件太大
2.inline只是向编译器发出请求,编译器有可能会忽略这个请求(当内联函数体过长等等)
3.inline不能声明和定义分离在两个文件中,会导致链接错误
#include
using namespace std;
inline int Add(int left, int right)
{
return left + right;
}
int main()
{
int ret = 0;
ret = Add(1, 2); //Add函数体内容会直接在该处展开,不会去调用Add函数
printf("%d\n", ret);
}
六、auto关键字
auto可以根据变量初始化表达式自动推导变量类型,无需显示指定变量类型
#include
using namespace std;
typedef char* pstring;
int main()
{
int a = 1;
auto b = a;
auto* c = &a; //与 auto c = &a 等价
auto& d = a; //auto声明引用类型必须加&
cout << "a:" << typeid(a).name() << endl; //typeid用于查看变量类型
cout << "a:" << typeid(b).name() << endl;
cout << "a:" << typeid(c).name() << endl;
cout << "a:" << typeid(d).name() << endl;
}
注意:
1.在同一行声明多个变量时,这些变量类型必须相同,因为编译器只会对第一个类型进行推导,然后根据推导出来的类型定义其他变量
#include
using namespace std;
typedef char* pstring;
int main()
{
//在同一行定义多个变量
auto a = 1, b = 2;//(√)
auto c = 3, d = 4.5 //(×)
}
2.auto不能作为函数参数,也不能声明数组
#include
using namespace std;
typedef char* pstring;
void func(auto x) //(×)
{
//···
}
int main()
{
func(1);
auto arr[] = { 1, 2, 3 };//(×)
}
七、基于范围的for循环
基本用法:
#include
using namespace std;
typedef char* pstring;
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
for (auto e : arr) //依次取数组中的值赋值给e,自动判断结束,自动迭代
{
cout << e << " ";
}
}
注意:基于范围的for循环只是把数组元素依次拿出来了
#include
using namespace std;
typedef char* pstring;
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
//arr中的值没有改变
for (auto e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e << " ";
}
}
#include
using namespace std;
typedef char* pstring;
int main()
{
int arr[5] = { 1, 2, 3, 4, 5 };
//arr中的值改变了
for (auto& e : arr)
{
e *= 2;
}
for (auto e : arr)
{
cout << e << " ";
}
}
八、指针空值nullptr(C++11)
我们知道C语言中是用NULL表示空指针,那么为什么C++要引入nullptr表示空指针呢?还是由于当初定义NULL时的缺陷
#include
using namespace std;
typedef char* pstring;
void func(int i)
{
cout << "func(int)" << endl;
}
void func(int* p)
{
cout << "func(int)" << endl;
}
int main()
{
func(0); //打印"func(int)"
func(NULL); //打印"func(int)"
}
我们本意是想构成函数重载,但是由于NULL被定义成了字面常量0或(void*)0,导致了上述问题
因此引入了关键字nullptr表示空指针,无需包含任何头文件
·································································································································
白日不到处,青春恰自来。苔花如米小,也学牡丹开,以此共勉~