1.关键字
2.命名空间
1.命名冲突问题
#include<stdio.h>
#include<stdlib.h>
int rand = 0;
int main()
{
scanf("%d", &rand);
printf("%d", rand);
return 0;
}
编译会发生错误
原因: 编译器在编译处理阶段第一步是把.h文件在.cpp里展开,<stdlib.h>就有rand函数。
命名冲突问题:1.自己定义的变量,函数可能跟库里面命名冲突2.同一项目多人协作,与别人写的代码,命名冲突 |
如何使用
首先我们先定义了一个叫zly的命名空间
//定义了一个叫zly的命名空间 -- 域
namespace zly
{
int rand = 0;
}
但此时打印的是stdlib.h里函数rand的地址
所以我们要用到::(域作用限定符)
printf("%d\n", zly::rand);
::在左边的域里面找,左边是空白就是全局域
1.命名空间还可以定义变量/函数/类型 |
#include<stdio.h>
#include<stdlib.h>
//定义了一个叫zly的命名空间 -- 域
namespace zly
{
int rand = 0;
int Add(int left, int right)
{
return right + right;
}
struct Node
{
struct Node* next;
int val;
};
}
int main()
{
zly::rand = 10;
zly::Add(1, 2);
struct zly::Node node;
return 0;
}
2.命名空间还可以嵌套定义 |
namespace zly
{
int rand = 0;
int Add(int left, int right)
{
return right + right;
}
struct Node
{
struct Node* next;
int val;
};
namespace cl
{
int c = 1;
int Sub(int left, int right)
{
return left - right;
}
}
}
调用cl里的函数只需要:
//先到zly里面找到cl的命名空间,再到cl的命名空间里找到Sub
zly::cl::Sub(9, 5);
3.同一工程中允许存在多个相同名称的命名空间,编译器最后会合并到一个命名空间 |
慎用下面的方法
不过我们可以单独展开某一个,其他不展开–用于展开命名空间中常用的
using zly::Node;
3.C++输入&输出
我们想打印可以使用:
#include<iostream>
//c++库的实现定义在一个叫std的命名空间中
using namespace std;
int main()
{
cout << "hello world" << endl;
return 0;
}
但是这样不是很好(平时练习使用),相当于把std的库全部展开了
可以使用:
#include<iostream>
int main()
{
std::cout << "hello world" << std::endl;
return 0;
}
我们也可以把常用的展开
using std::cout;
using std::endl;
int main()
{
cout << "hello world" << endl;
return 0;
}
“<<”可以看作流插入,cout可以看作控制台,endl可以看作换行。等价于printf("hello \n") |
特点:自动识别类型
using std::cout;
using std::endl;
int main()
{
cout << "hello world" << endl;
int i = 10;
double d = 1.34;
cout << i << " " << d << endl;
return 0;
}
cout和printf哪个方便用哪个 |
//cpp用法
cout << "姓名:" << s.name << endl;
cout << "年龄:" << s.age << endl<<endl;
//c语言
printf("姓名:%s\n年龄:%d\n", s.name, s.age);
cin和scanf,">>"流提取运算符 |
cin >> s.name >> s.age;
scanf("%s%d", s.name, &s.age);
4.缺省参数
概念:缺省参数是声明或定义函数时为函数的参数指定一个默认值。在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参
全缺省:所以参数都给了缺省值(默认值)
void Func(int a = 10, int b = 15, int c = 20)
{
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "c=" << c << endl<<endl;
}
int main()
{
Func();//abc的缺省值不改变
Func(1);//只传前一个
Func(1, 2);//只传前两个
Func(1, 2, 3);//传三个
}
半缺省:缺省部分参数—必须从右往左缺省,必须连续缺省
像void(int a=10,int b,int c)或者void(int a,int b=10,int c)都是不行的
void Func(int a, int b, int c = 20)
//某个参数必须传,放在前面,不一定要传就放在后面
{
cout << "a=" << a << endl;
cout << "b=" << b << endl;
cout << "c=" << c << endl<<endl;
}
int main()
{
Func(1, 2);//没有给缺省值必须要传
Func(1, 2, 3);
}
缺省参数不能在函数声明和定义中同时出现,要么在声明,要么在定义,推荐写在声明
这是错误的:
//a.h
void TestFunc(int a = 10);
// a.c
void TestFunc(int a = 20)
{}
5.函数重载
定义:C++允许在同一作用域中声明几个功能类似的同名函数,这些同名函数的
形参列表(参数个数 或 类型 或 顺序)必须不同。
//参数类型不同
int Add(int left, int right)
{
return left + right;
}
double Add(double left, double right)
{
return left + right;
}
//参数个数不同
void Func(int a, int b)
{
cout << "a=" << a << endl;
cout << "b=" << b << endl;
}
void Func(int a)
{
cout<<"a="<< a << endl;
}
//参数顺序不同
void Func(int a, char c)
{
cout << "a=" << a << endl;
cout << "c=" << c << endl;
}
void Func(char c,int a)
{
cout << "a=" << a << endl;
cout << "c=" << c << endl;
}
注意:1.返回值不同不能作为函数重载的依据 2.缺省值不同,不能构成重载 |
//返回值不同,调用的时候不能区分
int f(double d)
{
}
void f(double d)
{
}
//缺省值不同
void f(int a)
{
cout << "a=" << a << endl;
}
void f(int a = 10)
{
cout << "a=" << a << endl;
}
但是一个带参,一个不带参时,出现了问题 |
void f()
{
cout << "f()" << endl;
}
void f(int a=10)
{
cout << "f(int a)" << endl;
}
int main()
{
f();//调用存在歧义
return 0;
}
c语言不支持重载,而c++支持,接下来就是分析为什么 |
1.预处理 ->头文件展开、宏替换、条件编译、去掉注释
func.i test.i
2.编译 -> 检查语法,生成汇编代码
func.s test.s
3.汇编 ->汇编代码转换成二进制机器码
func.o test.o
4.链接
a.out
c语言不支持重载和c++支持就是链接部分出的问题
c语言不支持函数重载,因为编译的时候,两个重载函数,函数名相同,在func.o符号表中存在歧义和冲突,因为他们都是使用函数名去标识和查找,而重载函数函数名相同 |
c++的目标文件符号表中不是直接用函数名来标识和查找函数 |
1.函数名修饰规则,但是这个1修饰规则,不同的编译器下不同
Linux: -Z+函数名长度+函数名+参数首字母
2.有了函数名修饰规则,只要参数不同,func.o的符号表就不存在二义性和冲突了。
3.链接的时候,test.o的main的函数里面调用两个重载的函数,查找地址时,也是明确的
main 函数:
而c语言对函数名的处理:
6.引用
概念: 引用不是新定义一个变量,而是给已存在变量取了一个别名,它和已存在的变量共用一块空间
表达方式: 类型& 引用变量名=引用实体
比如:int a = 10; int& b = a;
引用在语法层,没有开辟新空间,而是对原来的空间取新名称,跟人的大名和绰号一样
引用的特性:
1.引用必须在定义时初始化
2.一个变量可以有多个引用
int a = 10;
int& b = a;
int& c = b;
int& d = c;
3.引用一旦引用一个实体,就不能引用其他实体
int a = 10;
int& b = a;
int c = 10;
//b不能变成c的别名
int& b = c;
//c的值不能赋给b
b = c;
应用
1.引用作为参数
最简单的交换函数:我们知道形参的改变不会影响实参,所以要想改变实参就要传实参的地址,但是传引用也能实现
void Swap(int a, int b)//声明//传值
{
int tmp = a;
a = b;
b = tmp;
}
void Swap(int& r1, int& r2)//既是声明也定义了//传址
{
int tmp = r1;
r1 = r2;
r2 = tmp;
}
void Swap(int* p1, int* p2)//传引用
{
int tmp = *p1;
*p1 = *p2;
*p2 = tmp;
}
这三个函数构成重载(参数的类型不同),但是Swap(a,b)
调用的时候会发生歧义,不知道调用传值还是传引用。
用引用解决单链表中传二级指针问题 |
int main()
{
SL* plist = NULL;
SeqListPushBack(&plist, 1);
SeqListPushBack(&plist, 2);
SeqListPrint(plist);
return 0;
}
void SeqListPushBack(SL** pphead, STDataType x)
{
SL* newnode = CreatListNode(x);
if (*pphead == NULL)
{
*pphead = newnode;
}
else
{
SL* tail = *pphead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
首先我们要理解,int 类型可以有别名,int* 同样也可以有,所以以下两种写法是对的
int a = 10;
int& b = a;
int* p1 = &a;
int*& p2 = p1;
所以单链表的尾插同样可以通过传引用实现
phead是plist的别名,phead的改变就是plist的改变
int main()
{
SL* plist = NULL;
SeqListPushBack(plist, 1);
SeqListPushBack(plist, 2);
SeqListPushBack(plist, 3);
SeqListPrint(plist);
return 0;
}
void SeqListPushBack(SL*& phead, STDataType x)
//phead是plist的别名,phead的改变就是plist的改变
{
SL* newnode = CreatListNode(x);
if (phead == NULL)
{
phead = newnode;
}
else
{
SL* tail = phead;
while (tail->next)
{
tail = tail->next;
}
tail->next = newnode;
}
}
2.引用作返回值
就拿Add函数举例
传值返回 |
int Add(int a, int b)
{
int c = a + b;
return c;
}
c不是真正的返回值,而是临时变量
c是局部变量,返回给ret的时候栈帧已经销毁,再取c就会非法访问
临时变量存在哪?
如果c比较小(4/8字节),一般寄存器充当临时变量
如果c比较大,临时变量存放在调用函数的栈帧中
所以我们要知道:所有的传值返回都会生成一个拷贝
传引用返回:不会生成c的拷贝返回,而是返回c的引用 |
int& Add(int a, int b)
{
int c = a + b;
return c;
}
代码的问题
1.存在非法访问,因为Add函数的返回值是c的引用,所以Add栈帧销毁以后,会去访问c位置的空间
2.如果Add函数栈帧销毁,清理空间(有可能会清理),那么取c值的时候就是随机值,给ret的就是随机值,取决于编译器
主函数这么调用时会发生问题:
int main()
{
int& ret = Add(1, 2);
cout << "ret:"<<ret << endl;
Add(10, 20);
cout <<"ret:"<< ret << endl;
return 0;
}
ret值改变了因为ret就是c的引用()
当cout函数占用了销毁了的空间,对c覆盖了,就会发生下面问题
int main()
{
int& ret = Add(1, 2);
cout << "ret:"<<ret << endl;
cout << "111111111111111111111"<< endl;
cout <<"ret:"<< ret << endl;
return 0;
}
所以一般不用传引用返回,所以什么情况能用传引用返回呢?
如果函数返回时,出了函数作用域,如果返回对象还在,可以用传引用返回,如果已经还给系统了,必须使用传值返回
比如:静态变量,全局变量,malloc出来的
//n是静态变量就可以使用传引用返回了
int& Add(int a,int b)
{
static int n=0;
n++;
return n
}
arr是全局变量
#include <time.h>
struct A{ int a[10000]; };
A a;
// 值返回 -- 每次拷贝40000byte
A TestFunc1() { return a; }
// 引用返回 -- 没有拷贝
A& TestFunc2(){ return a; }
或者下面出了作用域还存在
char& at(char* str,int i)
{
return str[i];//出了作用域,str[i]没有还给系统
}
int main()
{
char arr[10];
at(arr,3);
return 0;
}
malloc出来的
p不能返回,*p可以返回
int*& f()
{
int* p=(int*)malloc(4);
return *p;
}
引用的作用主要体现在传参和传返回值 1.引用传参和传返回值,有些场景可以提高性能 2.作输出型参数和输出型返回值。 |
权限放大(不可以)
const int a; a都是只读的,不能修改
权限不变(可以)
const int a = 10;
const int& b = a;
权限缩小(可以)
int c = 10;
const int& d = c;
在c语言中学过了类型转换
因为在类型转换的过程中,创建了临时变量,d先把整型部分给临时变量,临时变量再给i,临时变量具有常性,是右值,不可修改。用int& 来接收就发生错误,加个const就能避免错误. |
右值:通常是不能被修改的,比如:表达式产生的临时变量,常量
结论:const Type& 可以接收各种类型 |
引用的价值
1.作参数–提高效率
2.作返回值(不要返回局部变量的引用)意义:修改返回对象
传值返回,会拷贝给临时对象,临时对象作返回值。 传引用返回,返回的是返回对象的引用(别名) |
价值:a.提升效率 b.修改返回变量
修改返回变量
int& At(int i)//可读可写
{
static int a[N];
return a[i];
}
int main()
{
for (int i = 0; i < N; i++)
{
At(i) = 10 + i;//写操作
}
for (int i = 0; i < N; i++)
{
cout << At(i) << "" ;//读操作
}
cout << endl;
return 0;
}
如果去掉引用就会报错:
int At(int i)
{
static int a[N];
return a[i];
}
因为:临时变量具有常性,是右值(可读,但不能修改)
返回的不是a[i]了而是a[i]拷贝的临时变量
指针跟引用的区别 |
按自己的理解:1.指针是存储地址,引用是一个变量的别名。2.引用必须初始化,指针最好初始化。3.引用了一个实体后不能引用其他实体4.sizeof不同,sizeof(指针)==4个字节,sizeof(引用)==引用类型的大小
使用指针要考虑空指针,野指针等问题,指针太灵活了,相对而言,没有引用安全
语法层:指针和引用是完全不同的概念;指针是开空间,存储变量地址;引用不开空间,仅仅是对变量取别名 |
7.extern c
步骤参考下面博客
8.内联函数
定义一个Add函数,如果我们想多次调用
调用函数需要建立栈帧,栈帧中要保存一些寄存器,结束后要恢复
每次调用都会有消耗
在C语言中,我们可以用宏来替换Add函数,直接替换,没有消耗
#define Add(x,y) ((x)+(y))
//不加“;”,并且注意()
C++给了新的语法——内联函数
inline int Add(int x, int y)
{
int ret = x + y;
return ret;
}
反汇编之后,可以看到,内联函数直接展开,不是内联的还得调用
inline的特性:
1.inline是以空间换时间的做法,省去调用函数的额外开销。所以代码很长或者有递归,就不适合作为内联函数,调用的地方很多,展开后程序会变大
2. inline不建议声明和定义分离,分离会导致链接错误。因为inline被展开,就没有函数地址了,链接就会找不到,想用的话直接定义时内联
// F.h
#include <iostream>
using namespace std;
inline void f(int i);
// F.cpp
#include "F.h"
void f(int i)
{
cout << i << endl;
}
// main.cpp
#include "F.h"
int main()
{
f(10);
return 0;
}
// 链接错误:main.obj : error LNK2019: 无法解析的外部符号 "void __cdecl f(int)" (?
f@@YAXH@Z),该符号在函数 _main 中被引用
f.cpp->f.o 符号表中不会生成f函数的地址
因为inline函数是不需要地址的,都在调用的地方展开了
结论:短小,频繁调用的函数建议定义成inline |
auto关键字
const int a = 10;
//自动推导c的类型
auto c = a;//c的类型是int
//const int a=10;
//auto c = &a;//c的类型是int const*
auto d = 'a';//d是char类型
auto e = 10.22;//e是double类型
auto 只要类型,const的属性会丢失;auto 不能独立定义,比如auto a;
,必须给个值,因此auto不能作为参数;auto 也不能定义数组
auto的价值体现:语法糖:范围for |
遍历数组,原先和c++11的对比
int sz = sizeof(arr) / sizeof(arr[1]);
int i = 0;
for (i = 0; i < sz; i++)
{
cout << arr[i] << endl;
}
// c++11 范围for
//会自动依次取数组里面的每个元素,依次赋值给e,也会自动判断结束
for (auto e : arr)
{
cout << e << endl;
}
//可以用auto,也可以写清楚类型
for(int x : arr)
{
cout<< x <<endl;
}
在范围for 的基础上,如何把数组中每个值+1?
//把数组中每个值+1
for (auto e : arr)
{
e++;//这个方法不对,数组会把值赋给e,e++,并不会改变数组
}
//这个方法可以,数组里的每个值都赋值给e,e又是每个值的别名,e++,数组里每个值也++
//e的生命周期不是整个循环,而是一次循环
for (auto& e : arr)
{
e++;
}
指针空值nullptr
void f(int )
{
cout << "f(int)" << endl;
}
void f(int* )
{
cout << "f(int*)" << endl;
}
int main()
{
//c++98
int* p1 = NULL;
int* p2 = 0;
// c++11
int* p3 = nullptr;
f(NULL);//匹配 f(int );原因是:c++中通过宏把NULL替换成0
f(0);
f(nullptr);//f(int* );c++中通过宏把nullprt替换成void* 0
return 0;
}