类型& 引用变量的名称 = 变量名称
在类型名和变量名之间的&,作用是引用
引用在语法来说,是不开辟空间
int a= 10,b = 20;
int c = 0;
c = a&&b; //逻辑与
c = a & b; //按位与
int *p = &a; //取地址
int &x = a; //引用 x不开辟空间 x变了a也变了 x和a是同一块空间
int & fun() //函数返回类型为引用类型
{}
void Swap(int &a, int &b) //引用
{
int tmp = a;
a = b;
a = tmp;
}
从右向左:
可以定义引用来指向指针
不可以定义指针去指向引用
int a = 10,b = 20;
int *p =&a;
int *s = p;
int *&pref = p; √
//给p取了个别名pref
int &*pref = p; ×
底层是一个指针
从语法上来看 引用是一个变量的别名
引用相当于一个自身为常性的指针
int& fun() //× 局部常量不能返回引用
{
int a = 10;
return a;
}
int * const fun()
{
int a = 10;
return &a;
}
什么样的变量可以以引用返回?
此变量的生存周期不受函数生存期的影响
1.static
2.全局变量
3.以引用为参数进 以引用为参数返回
- 定义和性质不同。指针是一种数据类型,用于保存地址类型的数据,而引用可以看成是变量的别名。指针定义格式为:数据类型 *;而引用的定义格式为:数据类型 &;
- 引用不可以为空,当被创建的时候必须初始化,而指针变量可以是空值,在任何时候初始化;
- 指针可以有多级,但引用只能是一级;
- 引用使用时无需解引用(*),指针需要解引用;
- 指针变量的值可以是 NULL,而引用的值不可以为 NULL;
- 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了;
- sizeof 引用得到的是所指向的变量(对象)的大小,而 sizeof 指针得到的是指针变量本身的大小;
- 指针作为函数参数传递时传递的是指针变量的值,而引用作为函数参数传递时传递的是实参本身,而不是拷贝副本;
- 指针和引用进行++运算意义不一样。
const int a = 10;
int &b = a; //错误
const int& b = a; //常引用 √
int &c = (int&)a;
c+=100;
可以能力收缩 但不能能力扩展
可读可写–>可读不可写 (能力收缩 可以)
不能通过修改s的值来改变a的值
int a =10;
const int &s = a; 能力收缩
s+=19; //错的
注意:
可读不可写–>可读可写 (能力扩展 不行)
int main()
{
const int a = 10; //常变量 不能被修改
int &b = a; //不允许编译通过的
} //程序有二义性
引用的底层是一个指针
引用必须初始化
在使用到引用的地方,编译期会自动替换成指针的解引用
引用常量必须使用常引用
当引用一个不能取地址的量的时候,使用常引用
会生成一个临时量
引用临时量 临时量都有常属性
int fun()
{
return 10; //使用寄存器将数据返回
}
int main()
{
int &d = fun(); //❌ 修改为常引用
const int &d = fun();
}
Q:引用为什么必须初始化?
Q:引用为什么一旦初始化就无法改变引用的方向?
=> 在使用到引用的地方,编译期会自动替换成指针的解引用
C语言 == > 常变量
不能作为左值(可修改)
Const可以修饰返回值,如果返回值是值,则const无意义,如果返回值是指针,表示该指针不能被改动,只能把指针赋给const修饰的同类型的指针。
Const可以修饰成员方法,void fun()const{}代表在此函数中不允许对其数据成员做修改。在函数自己内部修改或者是调用普通成员方法修改数据成员都不可以!
const int a = 10; //常变量 不能被修改 必须被初始化
int* p = a;
int a= 10,b = 20;
int *p1 = &a;
*p1 = 100;
p1 = &b;
const int * p2 = &a; //*p2不能改变
//int x = *p2;
//p2 = &b; √
//*p2 = 100; error
int const * p3 = &a;
int * const p4 = &a;
//int x = *p4;
//*p4 = 100;
//p4 = &b; error
const int * const p5 = &a;
在const成员函数中,用mutable修饰成员变量名后,就可以修改类想成员变量
class A
{
private:mutable int m_count;
public:
A(int i):m_count(i) {};
int incr()const
{
return ++m_count;
}
int decr()const
{
return --m_count;
}
};
const int d =10 ;
int *p = (int)&d;
*p = 20;
//c中输出20 20 const 在输出时才去找const
//c++中输出10 20 const在编译时直接就放入到当前位置
const成员–必须放在初始化列表
//Person const *this
const char* get_name(/*Person * const this*/)const
{ //只能读取属性 不能改写
}
不属于函数重载 参数类型不相同
那些成员方法需要写成常方法:
1 如果成员方法内不需要改动成员,并且没有对外暴露成员引用||指针,就可以直接写成常方法
设计成为常方法后 不仅普通函数能够调用 常对象也可以调用
2 如果成员内部不需要改动成员,但是会对外暴露成员引用||指针,就写两个成员方法(const方法和非const方法),形成重载
3 如果成员方法内部需要改动成员,就写成普通方法
C++ ==> 常量
在编译期时将常量的值直接写入到常量的使用点
必须在使用时进行初始化否则之后就不能进行初始化
常量的初始化必须使用常量,如果使用变量给它赋值则会变成常变量
const int a = 10;
int *p = (int*)&a;//生成临时变量
*p = 20;//修改的就不是a的值
Q:为什莫常量必须初始化
因为在编译期时会将所有常量的位置全部替换为该常量的值
常量一经初始化就不能在修改它的值,编译期后就无法看见常量a
int d = 2;
const int e = d;
//如果使用变量给const修饰的量初始化,则该量会退化成常变量
const修饰的内容不能作为左值
不能泄露常量的地址给非常量的指针
const修饰的类型是离他最近的第一个成型的指针、其余全是它修饰的内容
如果const修饰的内容不包含指针,则无法参与类型
默认值参数
#include
using namespace std;
/*
入参从右向左入参
编译时期生成汇编指令
编译是针对单文件
*/
int fun(int a, int b, int c = 10)
{
cout << a << endl;
cout << b << endl;
cout << c << endl;
return 0;
}
int main()
{
int a = 1;
int b = 2;
fun(1, 2);
return 0;
}
//在fun.cpp定义fun函数
//在main.cpp声明fun函数
int fun(int a,int b,int c);
int main()
{
int a1 = 1;
int b1 = 2;
fun(a1,b1);//会出错 默认值参数在编译期时就要赋值,但在编译时只针对单文件,不能访问其他文件
return 0;
}
void fun(int a, int b, int c = 0, int d = 0,int e = 0) //默认值从右向左给
{
cout << a << b << c << d << e << endl;
//23 34 56 0 0
}
int main()
{
fun(23, (12,22,34), 56); //当有实参时会替换掉默认值
//逗号表达式 最后一个
}
首先带默认值参数的函数在调用时可以不用传递实参,并且形参的默认值必须是从右向左,只能给一次,
在调用带默认值参数的函数时,虽然他没有传入实参,但是从汇编层面来说还是生成push指令,将默认值压栈,所以在汇编层面还是没有提高效率
在c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存,现场保护,现场恢复的问题,开栈,清栈)特别引入inline修饰符
error :无法解析的外部符号
说明是函数有声明但是没有定义 或者是变量有被使用但是没有被定义
宏函数 | static函数 | 内联函数 | 普通函数 | |
---|---|---|---|---|
预编译展开 | 预编译时在调用点展开 | 不展开 | 不展开 | 不展开 |
调试 | 无法调试 | 可以调试 | 可以调试 | 可以调试 |
安全校验 | 没有类型安全校验 | 有类型安全校验 | 有类型安全校验 | 有类型安全校验 |
栈帧 | 没有栈帧空间开辟 | 有栈帧开辟 | debug版本有栈帧开辟,release版本没有栈帧开辟 | 有栈帧开辟 |
可见性 | 单文件可见 | 单文件可见 | 单文件可见 | 多文件可见 |
符号 | 不生成符号 | 生成local符号 | debug版本生成local符号(为release版本服务,生成local符号只针对当前文件可见) ,release版本不生成符号(编译时就直接展开) | 生成global符号 |
1.inline函数的处理时机是在编译时期处理的,有安全检查和类型检查,而宏的处理是在预编译阶段处理的,没有任何检查机制,只是简单的文本替换
2.inline函数是一种更安全的宏
内联函数调用时没有栈帧的开辟,内联函数只能在本文件作用域内可见,在release版本起作用,debug版本不起作用,相对与普通函数来说,普通函数在编译后要在符号表中生成符号,而内联函数在编译阶段是在调用点直接展开,不生成符号,无法进行链接
inline int fun1(int a,int b) //debug版本中 生成的是local符号
{
int c = a+b;
cout<<c<<endl;
return c;
}
符号
经过编译器编译后就会生成符号
数据:函数以外定义的变量;全局变量,静态变量
指令:函数体里面的代码生成指令
函数名相同,参数列表不同
不能仅通过函数返回类型来确定函数是否重载
函数重载是在编译时期决定究竟使用那个函数—静多态(或者早绑态、编译时期的多态)的一种
C语言生成函数符号依赖函数名
C++生成函数符号以来函数名和参数列表(返回值不影响)
回调方式 | @@YG |
c语言方式 | @@YA |
函数名结束 | @Z |
函数名开始 | ? |
int缩写 | ‘H’ |
double 缩写 | ‘N’ |
bool 缩写 | ‘N’ |
float 缩写 | ‘M’ |
//函数名
extern"C" int Max(int a, int b)
{ //告诉编译器用C语言来编译
//_Max
return a > b ? a : b;
}
//函数返回类型+函数名+参数列表(参数类型+参数个数)
extern"C++" int fun(int a, int b)
{//告诉编译器用C++来编译
return a + b;
}
bool compare(char a,char b){}
bool compare(float a,float b){}
bool compare(int a,int b){}
//compare(10.2,20.1) 默认是double类型的
//compare(10.2f,21.0f) float类型
C与C++相互调用
c++产生函数符号(函数名+参数列表)
C语言产生的函数符号(函数名)
extern "C" //c++调用c语言
{ //使用c语言的方式编译下面的代码
void fun_C();
}
C语言调用c++
namespace AA
{
typedef int INT;
};
//具有封装性
using namespace AA; //引用全部命名空间
using AA::INT; //使用命名空间中的
cout<<a<<endl; //输出
cin>>a>>endl; //输入
类型强转
int i = 3; //i自动转换为double类型
double p = 3.344;
i + p;
int *b = 0; //0转换为int*类型空指针值
i = p; //double 转换(被截取)为int
extern double sqrt(double);
cout<<sqrt(3)<<endl;
//3被提升为3.0
double dif(int i1,int i2)
{//返回的值的类型将被提升为double
return i1-i2;
}
算术转换通用原则:
short a = -1;
unsigned int b = 999;
if(a > b)
{
cout<<"-1>999"<<endl; //√
}
else{
cout<<"999 >-1"<<endl;
}
const int a = 10;
int *p = (int*)&a;
int*s = const_cast<int*>(&a); //可能修改a的值
指针和数组名的区别—什么是指针
int a = 1;
int *p = &a;
b = *p;
/*
下地址 == 解引用
去p的地址中将存储的地址拿出来
再去拿出来的地址中取出存储的数据
*/
动态内存
C++ new delete
C malloc free 堆上
区别:
ip = new(nothrow) int[n];
if(ip == NULL) exit(1);
int* p = new int; //不用判空 没有申请成功会抛出异常
*p = 10;
delete p;
p = NULL;
int* arr = new int [10]; //动态开辟一维数组
arr[0] = 10;
delete []arr;
int** arr1 = new int*[4]; //动态开辟二维数组
for(int i = 0;i<4;i++)
{
arr1[i] = new int[5];
}
for(int i=0;i<4;i++) //动态释放二维数组
{
delete []arr[i];
}
delete[] arr1;
int* arr = (int*)malloc(sizeof(int));
free(arr);
int** brr = (int**)malloc(sieof(int*)*row);
for(int i = 0;i<row;i++)
{
brr[i] = (int*)malloc(sizeof(int)*col);
}
for(int i=0;i<row;i++)
{
free(brr[i]);
}
free(brr);
解决全局变量的污染问题,解决命名冲突的问题
namespace lee
{
int a;
int Sum(int a,int b)
{return a+b;
}
}//全局变量a和函数Sum定义在lee这个名字空间作用域下,将其隔离起来。
int main()
{
//1
cout << lee::a << endl;
cout << lee::Sum(10, 20) << endl;
return 0;
}
int main(){
//2
using namespace lee;
cout << a << endl;
cout << Sum(1, 2) << endl;
return 0;
}
int main()
{ //3
using lee::a;
using lee::Sum;
cout << a << endl;
cout << Sum(1, 22) << endl;
}
如果同一个项目中出现同名的名字空间作用域则将同名的名字空间作用域进行合并,合并成一个名字空间作用域,包含所有名字空间作用域中的内容