有参考课件,仅供本人学习用
1.命名空间
如果两个人写的库文件中出现同名的变量或函数(不可避免),使用起来就有问题,为了解决这个问题,引入了名字空间这个概念
1.1定义
定义命名空间,需要使用到namespace关键字,后⾯跟命名空间的名字,然后接⼀对{}即可,{}中 即为命名空间的成员。命名空间中可以定义变量/函数/类型等。
namespace smc//命名空间名字通常可以使用自己的名字,形目名称等(此处均使用本人名字)
{
int a = 1;
// 命名空间中可以定义变量/函数/类型
int Add(int left, int right)
{
return left + right;
}
struct Node
{
struct Node* next;
int data;
};
}
namespace本质是定义出⼀个域,这个域跟全局域各自独立,不同的域可以定义同名变量。局部域和全局域除了会影响 编译查找逻辑,还会影响变量的⽣命周期,命名空间域和类域不影响变量⽣命周期。
namespace smc
{
int a = 1;
}
int a = 2;
int main()
{
int a = 3;
// ::运算符,分为三种:全局作用域符,类作用域符,命名空间作用域符
printf("%d\n", a);//优先查找局部变量
printf("%d\n", smc::a);
printf("%d\n", ::a);//全局
}
namespace只能定义在全局,为防止命名空间里冲突还可以嵌套定义
namespace smc
{
namespace n1
{
int a = 1;
}
namespace n2
{
int a = 2;
}
}
int main()
{
printf("%d ", smc::n1::a);
printf("%d ", smc::n2::a);
}
项⽬⼯程中多⽂件中定义的同名namespace会认为是⼀个namespace,不会冲突。
//stack.h
namespace smc
{
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
}
//stack.cpp
namespace smc
{
void STInit(ST* ps, int n)
{
assert(ps);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
}
//虽然在不同文件,但会默认合并到一起,就像一个命名空间一样
1.2命名空间的使用
编译查找⼀个变量的声明/定义时,默认只会在局部或者全局查找,不会到命名空间⾥⾯去查找。所以我们要使⽤命名空间中定义的变量/函数,有三种方式:
- 指定命名空间访问
printf("%d ", smc::a);
-
展开命名空间中全部成员,不推荐,冲突风险很大
namespace smc { int a = 0; int b = 1; } using smc; int main() { printf("%d ", a); printf("%d ", b); }
using将命名空间中某个成员展开,项⽬中经常访问的不存在冲突的成员推荐这种⽅式。
namespace smc
{
int a = 0;
int b = 1;
}
using smc::a;
int main()
{
printf("%d ", a);
printf("%d ", a);
printf("%d ", a);
printf("%d ", smc::b);
}
2.输入输出
头文件:<iostream>
std::cin 标准输入流
std::cout 标准输出流
std::endl 是⼀个函数,流插⼊输出时,相当于插⼊⼀个换行字符加刷新缓冲区
<< 流插入运算符,>> 流提取运算符(不需要手动指定格式,自动识别变量类型 )
注:cout/cin/endl等都属于C++标准库,C++标准库都放在⼀个叫std(standard)的命名空间中,所以要通过命名空间的使用方式去用他们。⼀般⽇常练习中我们可以using namespace std,实际项⽬开发中不建议
#include<iostream>
using namespace std;//或者调用时使用::操作符
//using std::cin;
int main()
{
int a = 1;
double b;
char c;
std::cout << a << std::endl;
cin >> c >> b;//自动识别类型
std::cout << c << " " << b;
return 0;
}
3.缺省参数
缺省参数是声明或定义函数时为函数的参数指定⼀个缺省值。在调用该函数时,如果没有指定实参则采用该形参的缺省值,否则使用指定的实参(缺省参数也叫默认参数)
全缺省就是全部形参给缺省值,半缺省就是部分形参给缺省值。C++规定半缺省参数必须从右往左依次连续缺省,不能间隔跳跃给缺省值。
带缺省参数的函数调用,C++规定必须从左到右依次给实参,不能跳跃给实参。
函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现,规定必须函数声明中给缺省值
#include<iostream>
void print(int i = 1)
{
std::cout << i << std::endl;
}
int main()
{
print();//未传参,使用缺省值 1
print(10);//10
return 0;
}
// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
// 半缺省
void Func2(int a, int b , int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func1();
Func1(1);
Func1(1, 2);
Func1(1, 2, 3);
Func2(1, 2);
Func2(1, 2, 3);
return 0;
}
4.函数重载
C++⽀持在同⼀作⽤域中出现同名函数,但是要求这些同名函数的形参不同
参数个数不同, 类型不同,参数类型顺序不同
构成歧义用例:
当调用f1()时,编译器不知道调用哪个函数,构成歧义
void f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
int main()
{
f1();
f1(10);
return 0;
}
5.引用
5.1定义
引用不是新定义⼀个变量,而是给已存在变量取了⼀个别名,不另外开辟空间
类型& 引用别名 = 引用对象
int main()
{
int a = 1;
int& b = a;
int& c = a;//b和c是a的别名
int& d = c;//d是c的别名
//实际上,abcd都指向同一个地址
return 0;
}
5.2特点
• 引用在定义时必须初始化
• 一个变量可以有多个引用。引用一旦指向一个实体,就不能在引用其他实体
5.3使用
引⽤在实践中主要是于引用传参和引用做返回值中减少拷贝提高效率和改变引用对象时同时改变被引用对象。
引用传参替代指针传参,跟指针传参功能是类似的,引用传参相对更方便⼀些。
void Swap(int& x, int& y)
{
int tmp = x;
x = y;
y = tmp;
}
int main()
{
int a = 10;
int b = 20;
Swap(a,b);
}
//typedef struct ListNode
//{
// int val;
// struct ListNode* next;
//}LTNode;
//
//void ListPushBack(LTNode** phead, int x);
//
//int main()
//{
// LTNode* plist = NULL;
// ListPushBack(&plist, 1);
// return 0;
//}
typedef struct ListNode
{
int val;
struct ListNode* next;
}LTNode, *PNode;
// 指针变量也可以取别名,这⾥LTNode*& phead就是给指针变量取别名
//void ListPushBack(LTNode** phead, int x)
//void ListPushBack(LTNode*& phead, int x)
void ListPushBack(PNode& phead, int x);
int main()
{
PNode plist = NULL;
ListPushBack(plist, 1);
return 0;
}
引用返回值
// int STTop(ST& rs)
int& STTop(ST& rs)
{
assert(rs.top > 0);
return rs.a[rs.top];
}
int main()
{
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
STTop(st1) += 10;//可以直接修改取到的栈顶元素
return 0;
}
//不能使用引用返回值用例:
//返回临时变量地址问题
#include<iostream>
int& func()
{
int ret = 1;
ret++;
return ret;//ret是局部变量,出函数销毁
}
int main()
{
int ret=func() += 1;
std::cout << ret;
return 0;
}
注:越界不一定报错
越界读不报错,越界写不一定报错,一般是抽查
5.4const引用
const引用对象:普通对象,const对象,临时对象
int main()
{
//1.引用const对象时必须用const
const int a = 1;
const int& ra = a;
//2.引用普通对象时,注意权限问题
const int a = 1;
//int& ra = a;权限不能放大
//这样写ra就能修改了
int a = 2;
const int& ra = a;//权限可以缩小
return 0;
}
需要注意的是类似 int& rb = a*3; double d = 12.34; int& rd = d; 这样⼀些场 景下a*3的和结果保存在⼀个临时对象中, int& rd = d 也是类似,在类型转换中会产⽣临时对象存储中间值,也就是时,rb和rd引⽤的都是临时对象,⽽C++规定临时对象具有常性,所以这里就触发了权限放大,必须要用常引用才可以。
//3.临时对象
int main()
{
int a = 1;
int b = 10;
const int& rb = a * 3;
double c = 1.1;
const int& rd = c;
return 0;
}
5.5对比指针
6.inline
inline修饰的函数叫做内联函数,编译时C++编译器会在调用的地方展开内联函数,这样调用内联 函数就不需要建立栈帧了,就可以提高效率。
nline对于编译器只是⼀个建议,加了inline编译器也可以选择在调用的地方不展开,inline适用于频繁调用的短小函数,对于递归函数,代码相对多⼀些的函数,加上inline也会被编译器忽略。
inline int add(int x, int y)
{
int ret = x + y;
ret++;
return ret;
}
int main()
{
int ret = add(1, 2);
return 0;
}
反汇编层面理解:
注:inline函数不支持声明和定义分离开,因为编译器一旦将一个函数作为内联函数处理,就会在调用位置展开,即该函数是没有地址的,也不能在其他源文件中调用,故一般都是直接在源文件中定义内联函数的
C语言实现宏函数也会在预处理时替换展开,但是宏函数实现很复杂很容易出错的,且不便调 试,C++设计了inline目的就是替代C的宏函数。
回顾宏定义
//正确宏定义
#define Add(x,y) ((x)+(y))
int main()
{
int ret=Add(1, 2);
cout << ret;
return 0;
}
// 为什么不能加分号?
// 为什么要加外⾯的括号?
// 为什么要加⾥⾯的括号?
宏参数是完全替换,后面不需要加分号,建议多加括号,避免出现运算级先后问题
宏函数缺点很多,但是由于其替换机制,调用时不用建立栈帧,提高效率
7.nullptr
NULL实际是⼀个宏,在传统的C头⽂件(stddef.h)中,可以看到如下代码:
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
C++中NULL可能被定义为字面常量0,或者C中被定义为无类型指针(void*)的常量
nullptr是⼀个特殊的关键字,它可以转换成任意其他类型的指针类型。使⽤nullptr定义空指针可以避免类型转换的问题,因为nullptr只能被隐式地转换为指针类型,不能被转换为整数类型
void f(int x)
{
cout << "f(int x)" << endl;
}
void f(int* ptr)
{
cout << "f(int* ptr)" << endl;
}
int main()
{
f(0);
f(NULL);//此处还是调用的第一个函数,因为NULL被定义为0
//要想调用第二个函数应如下:
f(nullptr);
return 0;
)