1.关键字
2.命名空间
局部->全局 命名空间需要展开(编译时是否会去命名空间里搜索)或指定展开相当于全局变量
优先局部搜索,局部没有才去全局,左边域空白即为全局域
只有全局域和命名空间域回去全局域搜索,命名空间域默认情况下不会去搜索
除非展开命名空间域或者指定访问命名空间域
#include<stdio.h>
#include<stdlib.h>
int a=0;
//类域
//命名空间域
//局部域
//全局域
namespace bit
{
int a=2;
}
int main()
{
int a=1;
printf("%d\n",a);
//::域作用限制符
printf("%d\n",::a);
printf("%d\n",bit::a);
return 0;
}
//输出:
//1
//0
//2
当命名空间展开时相当于把变量暴露到全局
int a=0;
namespace bit
{
int a=1;
}
int main()
{
printf("%d\n",a);
return 0;
}
//会报错
c++为了防止命名冲突,将标准库的东西都放到std中
#include<iostream>
using namespace std;
int rand =0;
int main()
{
printf("%d\n",rand);
printf("%d\n",std::rand);
return 0;
}
//会报错rand会到std命名空间去寻找
1.在项目中尽量不要使用using namespace
2.日常练习使用using namespace
3.项目中可以指定命名空间访问+展开常用的
using std::cout;
using std::endl;
int main()
{
cout<<"hello"<<endl;
}
命名空间可以定义函数变量类型,可以嵌套,可以存在多个相同名字的命名空间编译时会被合并 同一命名空间命名冲突会报错解决方案为:嵌套命名空间
3.输入输出
using namespace std;
int main()
{
//特点:自动识别类型
int i;
double d;
//流提取
cin>>i>>d;
//流插入
cout<<i<<endl;
cout<<d<<endl;
return 0;
}
4.缺省参数
void Func(int a=0)
{
cout<<a<<endl;
}
int main()
{
Func(1);//传参时,使用参数的默认值
Func();//没有传参时,使用指定的实参
}
4.1全缺省
void TextFunc(int a=10,int b=20,int c=30)
{
cout<<a;
cout<<b;
}
int main()
{
TestFunc(1)
//传参默认从左往右进行传参不可以固定位置进行传参,如TestFunc(,,1)
}
4.2半缺省
缺省部分参数
void TextFunc(int a,int b=20,int c=30)
{
//必须从右往左缺省,不能间隔
cout<<a;
cout<<b;
}
int main()
{
TestFunc(1);
}
4.3运用举例
struct Stack
{
int * _a;
int _top;
int _capacity;
};
void StackInit(struct Stack* ps,int capacity=4)
{
ps->a=(int*)malloc(sizeof(int)*capacity);
ps->top=0;
ps->capacity=capacity;
}
注意:
1.缺省参数不能在函数声明和定义中同时出现(防止声明和定义时的缺省值不同,一般只在声明中给出)
2.半缺省参数必须从右往左依次来给出,不能间隔给
3.缺省值必须是常量或者全局变量
4.C中不存在缺省参数(编译器不支持)
5.函数重载
函数重载:是函数的一种特殊情况,c++允许在同一作用域中声明几个功能类似的同名函数这些同名函数的形参列表(参数个数或类型或顺序)必须不同,常用来处理实现功能类似数据类型不同的问题
int Add(int left,int right)
{
return left+right;
}
double Add(double left,double right)
{
return left+right;
}
返回值不同不构成重载
short Add(short left,short right)
{
return left+right;
}
int Add(short left,short right)
{
return left+right;
}
//不构成重载
为什么c++支持重载,c不支持?
编译链接
第一步预处理:头文件展开,宏替换,条件编译,去掉注释.....
生成.i文件
第二步编译:检查语法,生成汇编代码
生成.s文件
第三步汇编(指令语言):汇编代码转换成二进制机器码
生成.o文件
第四步链接:生成可执行程序
生成.exe .out文件
6.引用
引用不是重新定义一个变量,而是给已存在的变量取一个别名,编译器不会为引用变量开辟内存空间,它和它引用的变量共用一块内存空间
int a=0;
int& b=a;//引用
6.1引用特征
1.引用在定义时必须初始化
2.一个变量可以有多个引用
3.引用一旦引用一个实体,再不能引用其他实体
是赋值不是别名
6.2引用的使用场景
6.2.1做参数
a.输出型参数
void Swap(int& r1,int& r2)
{
int tmp=r1;
r1=r2;
r2=tmp;
}
b.大对象(深拷贝对象)传参,提高效率
6.2.2做返回值
优点
1.可以减少拷贝,提高效率。深拷贝或者大对象时比较方便
2.可以获取和修改返回值
传值返回
//传值返回
int Count()
{
static int n=0;
n++;
return n;//n在静态区只要是传值返回都会生成一个返回对象的临时拷贝作为函数的返回值
}
int main()
{
int ret=Count();//用n的拷贝作为返回值
return 0;
}
///
int Count()
{
int n=0;
n++;
return n;//n在count栈帧里会销毁,所以返回的是n的拷贝
}
int main()
{
int ret=Count();
return 0;
}
传引用返回
传引用返回可以减少拷贝提高效率,但是局部变量的传引用返回存在一定风险,如下程序所示。
int& Count()
{
int n=0;
n++;
return n;//返回返回对象的别名(引用)
}
int main()
{
int ret=Count();//ret的值是不确定的
//如果count函数结束,栈帧销毁,没有清理栈帧,那么ret结果侥幸是正确的。
//如果栈帧销毁则ret是随机值。
cout<<ret<<endl;
return 0;
}
int& Count(int x)
{
int n=x;
n++;
return n;//返回返回对象的别名
}
int main()
{
int& ret=Count(10);
cout<<ret<<endl;
Count(20);
//printf("ssssssssss");
cout<<ret<<endl;//ret为随机值
return 0;
}
//存在越界访问(ret为n的别名是一个销毁了的变量)
调用count函数(调用任意函数相同)覆盖了原栈帧内容(覆盖成什么是无法确定的)因此并未给ret赋值其值仍发生了改变
//引用返回的正确使用方式
int& Count()
{
int static n=0;
n++;
return n;
int main()
{
int& ret=Count();
printf("%d\n",ret);
printf("%d\n",ret);
return 0;
}
struct SeqList
{
int a[100];
size_t size;
};
//获取并修改顺序表某位置值
void SLGet(SeqList* ps,int pos)
{
assert(pos<100&&pos>=0);
return ps->a[pos];
}
void SLModify(SeqList* ps,int pos,int x)
{
assert(pos<100&&pos>=0);
ps->a[pos]=x;
}
//使用引用
int& SLAT(SeqList* ps,int pos)
{
assert(pos<100&&pos>=0);
return ps->a[pos];
}
int main()
{
SeqList S;
SLModify(&s,0,1);
//对第0个位置的值+5
int ret=SLGet(&s,0);
SLModify(&s,0,ret+5);
SLAT(&s,0)=1;
cout<<SLAT(&S,0)<<endl;
SLAT(&s,0)+=5;
return 0;
}
//C++写法
struct SeqList
{
int a[100];
size_t size;
int& at(int pos)
{
assert(pos>=0&&pos<100);
return a[pos];
}
int& operator[](int pos)
{
assert(pos>=0&&pos<100);
return a[pos];
}
};
int main()
{
SeqList s;
s.at(0)=0;
s.at(0)++;
return 0;
cout<<s.at(0)<<endl;
s[1]=10;
s[1]++;
cout<<s[1]<<endl;
}
总结
1.基本所用情况下都可以用引用传参
2.谨慎使用引用作为返回值,出了函数作用域,对象已经销毁时就不能用引用返回。
6.2.3常引用
int main()
{
//引用过程中权限不能放大
const int a=0;
int& b=a;//错误的
int c=a;//此处可以是一个拷贝c的改变不影响a
int x=0;
int& y=x;
const int& z=x;//正确引用过程中权限可以平移或者缩小
++x;//正确的,z会发生改变
++z;//不可以
const int& m=10;//可以
double dd=1.11;
int ii==dd;//可以,发生类型转换时会产生临时变量
int& rii =dd;//不可以,发生类型转换时会产生临时变量无法将临时变量赋值给rii,因为临时变量具有常性(相对于扩大了权限)
const int&rii =dd;//可以
return 0;
}
int func1()
{
static int x =0;
return x;
}
int& func1()
{
static int x =0;
return x;
}
int main()
{
int ret1=func1;可以
int& ret2=func1;//不可以
const int& ret3=func1();//可以权限的平移
int& ret4=func2();//权限平移
const int& ret2=func2();//权限缩小
return 0;
}
int main()
{
int a=10;
//语法层面:不开空间,是对a取别名
int& ra=a;
ra=20;
//语法层面:开空间,存储a的地址
int* pa=&a;
*pa=30;
return 0;
6.3引用与指针
从底层汇编指令实现的角度看引用是类似指针的方式实现的
1.引用概念上定义一份变量的别名,指针存储一个变量的地址
2.引用在定义时必须初始化,指针没有要求
3.引用在初始化时引用一个实体后就不能在引用其他实体,指针可以在任何时间指向任何一个用类型的实体
4.没有NULL引用但是有NULL指针
5.在sizeof中含义不同,引用的结果为引用类型大小,但指针始终是地址空间所占字节数
6.引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小
7.有多级指针但是没有多级引用
8.访问实体的方式不同,指针需要显式解引用,引用编译器自己处理
9.引用比指针的使用相对安全
7.内联函数
#define Add(x,y)(x+y)*10//宏函数没有;当Add(a|b,a&b)时因为算数运算符优先级高会优先计算加不符合预期
#define Add(x,y)((x)+(y))//正确写法
宏函数不需要建立栈帧提高调用效率
缺点:复杂,易出错,代码可读性变差无法调试
//适用于短小的频繁调用的函数(代码过长会造成代码膨胀)
//inline对于编译器仅仅是一个建议是否为内联函数由编译器决定以下函数不会成为内联函数
//比较长的函数
//递归函数
//默认debug下inline不会起作用,否则不方便调试
inline int Add(int x,int y)
{
return (x+y)*10;
}
int main()
{
for(int i=0;i<10000;i++)
{
cout<<Add(i,i+1)<<endl;
}
}
右键项目属性->常规->调试信息格式选择数据库
属性->优化->内联函数扩展选择只适用于inline后重新编译
//Func.h
#program once
#inlcude <iostream>
using namespace std;
inline void f(int i);
//Test.cpp
#include "Func,h"
int main()
{
f(10);//只有函数声明可以过编译但是链接找不到内联函数会出错
return 0;
}
//Func.cpp
void f(int i)
{
cout<<i<<endl;
}
内联函数声明定义不能分离内联函数直接写在头文件里
//Func.h
#program once
#inlcude <iostream>
using namespace std;
inline void f(int i)
{
cout<<i<<endl;
}
8.auto关键字
int main()
{
int a=0;
int b=a;
auto c=a;//根据右边表达式自动推导c的类型
auto d=1+1.11;//根据右边表达式自动推导d的类型
cout<<typeid(c).name()<<endl;//打印数据的类型
}
int main()
{
auto a=&x;//指针
auto* b=&x;//x必须为指针否则会报错
auto& c=x;//引用
int arr[]={1,2,3};
for(auto& x:arr)
{
x++;
}
for(auto& x:arr)
{
cout<<x<<endl;
}
}
9.基于范围的for循环
int main()
{
int arr[]={1,2,3,4,5};
for(int i=0;i<sizeof(arr)/aizeof(arr[0]);++i)
arr[i]*=2;
//范围for
//依次取数组中的数据赋值给e(无法修改数据,可以使用引用对数据进行修改)
//自动迭代自动判断结束适用于数组
for (auto e :arr)
{
cout<<e<<"";
}
cout<<endl;
//使用引用实现对数组的修改
//e为自定义的名字
for (auto& e :arr)
{
e*=2;
}
for (int& e :arr)
{
cout<<e<<"";
}
return 0;
}
9.1范围for使用条件
1.for循环迭代的范围必须是确定的,对于数组来讲就是数组中第一个元素和最后一个元素的范围;对于类而言,应该提供begin和end方法,begin,end就是for循环迭代范围
void testFor(int array[])
{
for(auto& e:array)
cout<<e<<endl;
}
int main()
{
return 0;
}
10.指针空值nullptr
void f(int)
{
cout<<"f(int)"<<endl;
}
void f(int*)//c++可以不接收形参
{
cout<<"f(int*)"<<endl;
}
int main()
{
f(0);
f(NULL);//c++都调用第一个
}
注意
1.使用nullptr表示指针空值时,不需要包含头文件,nullptr在C++11作为关键字引入
2.在c++11中sizeof(nullptr)与sizeof((void*)0)所占字节数相同
3.为提高代码健壮性,后续使用指针空值时最好使用nullptr