c++:C与C++的区别(引用 const 内联)

文章目录

    • 引用
      • 定义
      • 引用的本质
      • 指针,引用区别
      • 常引用:const和引用之间的关系
      • 引用(Zhao)
    • const常量
      • const(Yang)
      • 面试:const修饰的函数 如何进行修改
      • const 常方法
      • const和指针
    • 默认值参数
      • 面试:普通函数与带默认值参数的函数之间的区别
    • inline(内联)函数
      • 引入
      • 内联函数
      • 函数比较
      • 面试:内联函数与宏的区别
      • 面试:内联函数与普通函数的区别
    • 符号
    • 重载
      • 函数重载
      • 面试:为什么c++可以函数重载而c语言不可
        • 1.c和c++编译函数名的不同
        • 2.调用约定
      • c与c++相互调用
    • 类型强转
      • 隐式类型转换
    • 动态内存
    • namespace命名空间

引用

定义

类型& 引用变量的名称 = 变量名称
在类型名和变量名之间的&,作用是引用
引用在语法来说,是不开辟空间

  1. 当定义成引用时,必须给予初始化
  2. 在定义引用时,不能有空引用
  3. 没有引用的引用
  4. 引用一旦初始化后,就不能再引用其他数据
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;     ×

引用的本质

底层是一个指针
从语法上来看 引用是一个变量的别名
引用相当于一个自身为常性的指针
c++:C与C++的区别(引用 const 内联)_第1张图片

int& fun()    //× 局部常量不能返回引用
{
	int a = 10;
	return a;
}
int * const fun()
{
	int a = 10;
	return &a;
	}

什么样的变量可以以引用返回?
此变量的生存周期不受函数生存期的影响
1.static
2.全局变量
3.以引用为参数进 以引用为参数返回

指针,引用区别

  1. 定义和性质不同。指针是一种数据类型,用于保存地址类型的数据,而引用可以看成是变量的别名。指针定义格式为:数据类型 *;而引用的定义格式为:数据类型 &;
  2. 引用不可以为空,当被创建的时候必须初始化,而指针变量可以是空值,在任何时候初始化;
  3. 指针可以有多级,但引用只能是一级;
  4. 引用使用时无需解引用(*),指针需要解引用;
  5. 指针变量的值可以是 NULL,而引用的值不可以为 NULL;
  6. 指针的值在初始化后可以改变,即指向其它的存储单元,而引用在进行初始化后就不会再改变了;
  7. sizeof 引用得到的是所指向的变量(对象)的大小,而 sizeof 指针得到的是指针变量本身的大小;
  8. 指针作为函数参数传递时传递的是指针变量的值,而引用作为函数参数传递时传递的是实参本身,而不是拷贝副本;
  9. 指针和引用进行++运算意义不一样。

常引用:const和引用之间的关系

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; //不允许编译通过的 
}  //程序有二义性

引用(Zhao)

  • 引用的底层是一个指针

  • 引用必须初始化

  • 在使用到引用的地方,编译期会自动替换成指针的解引用

  • 引用常量必须使用常引用

  • 当引用一个不能取地址的量的时候,使用常引用
    会生成一个临时量
    引用临时量 临时量都有常属性

int fun()
{
return 10;   //使用寄存器将数据返回
}
int main()
{
  int &d = fun(); //❌ 修改为常引用
  const int &d = fun();
  
  }

Q:引用为什么必须初始化?
Q:引用为什么一旦初始化就无法改变引用的方向?
=> 在使用到引用的地方,编译期会自动替换成指针的解引用

const常量

const(Yang)

  • 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;

c++:C与C++的区别(引用 const 内联)_第2张图片
c++:C与C++的区别(引用 const 内联)_第3张图片
c++:C与C++的区别(引用 const 内联)_第4张图片
c++:C与C++的区别(引用 const 内联)_第5张图片

面试:const修饰的函数 如何进行修改

在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 常方法

const int d =10 ;
int *p = (int)&d;
*p = 20;
//c中输出20 20 const 在输出时才去找const
//c++中输出10 20  const在编译时直接就放入到当前位置 

const成员–必须放在初始化列表

  1. 常对象只能调用常方法–构造函数 析构函数 静态函数不影响
  2. 常对象不能调用普通方法
                     //Person const *this
const char* get_name(/*Person * const this*/)const
{ //只能读取属性 不能改写
 }

在这里插入图片描述

  • const放在方法名的前面表示该方法返回值是常量;const放在函数名的后面表示该方法只能读取属性值,不能修改属性值

c++:C与C++的区别(引用 const 内联)_第6张图片

  • 不属于函数重载 参数类型不相同

    那些成员方法需要写成常方法
    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修饰的类型是离他最近的第一个成型的指针、其余全是它修饰的内容
如果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指令,将默认值压栈,所以在汇编层面还是没有提高效率

inline(内联)函数

引入

在c++中,为了解决一些频繁调用的小函数大量消耗栈空间(栈内存,现场保护,现场恢复的问题,开栈,清栈)特别引入inline修饰符

内联函数

  • 内联函数在release版本才起作用,在debug版本不起作用
  • 在release版本,在调用内联函数时,该函数会在调用点展开—编译时期展开; //不可调试版本 发行版,在debug版本,内联函数和正常函数调用方式一致;
  • 由于内联函数在编译期展开,编译期无法获取变量的值,而递归函数的终止条件一定需要有变量参与,所以,递归函数不可能被处理为内联函数
  • inline只是对编译器的建议,建议将该函数处理为内联,具体是否处理成inline函数是编译器决定 的
  • inline函数在debug版本中生成的是local符号,如果处理为内联之后在release版本不生成符号,直接在调用点展开
  • 处理成inline函数的函数中不能存在if语句或循环语句

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;
}

符号

符号
经过编译器编译后就会生成符号

  • 所有的数据都会生成符号
  • 函数中只有函数名会生成符号
  • 全局符号 global符号 所有的文件只要引用声明就可以使用
  • 局部符号 local符号 只在本文件可见

数据:函数以外定义的变量;全局变量,静态变量
指令:函数体里面的代码生成指令

重载

函数重载

函数名相同,参数列表不同

  • 不能仅通过函数返回类型来确定函数是否重载

  • 函数重载是在编译时期决定究竟使用那个函数—静多态(或者早绑态、编译时期的多态)的一种

C语言生成函数符号依赖函数名
C++生成函数符号以来函数名和参数列表(返回值不影响)

面试:为什么c++可以函数重载而c语言不可

1.c和c++编译函数名的不同
  1. c:只依赖函数名
    c++:C与C++的区别(引用 const 内联)_第7张图片
  2. c++:函数返回类型+函数名+参数列表(参数类型+参数个数)
    c++:C与C++的区别(引用 const 内联)_第8张图片
2.调用约定
回调方式 @@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++相互调用
c++产生函数符号(函数名+参数列表)
C语言产生的函数符号(函数名)

extern "C"     //c++调用c语言
{             //使用c语言的方式编译下面的代码
      void fun_C();
}

C语言调用c++

  • 如果将c++的函数符号改为c语言的函数符号—添加自己实现的c++文件,写c++函数作为中间层去调用需要的c++函数,然后让自实现的c++
namespace AA
{
    typedef int INT;
};
//具有封装性
using namespace AA;   //引用全部命名空间
using AA::INT; //使用命名空间中的
cout<<a<<endl; //输出
cin>>a>>endl;  //输入

类型强转

类型强转

隐式类型转换

  1. 混合类型:目标转换类型是最宽的数据类型
int i = 3;     //i自动转换为double类型
double p = 3.344;
i + p;
  1. 一种类型给另一种类型赋值:目标转换类型是被赋值对象的类型
int *b = 0;    //0转换为int*类型空指针值
i = p;         //double 转换(被截取)为int
  1. 表达式传递给函数时,调用表达式的类型与形参类型不同:目标转换类型是形参的类型
extern double sqrt(double);
cout<<sqrt(3)<<endl; 
//3被提升为3.0
  1. 函数返回表达式的类型与返回类型不相同:目标转换类型是函数返回类型
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的值

c++:C与C++的区别(引用 const 内联)_第9张图片
c++:C与C++的区别(引用 const 内联)_第10张图片

指针和数组名的区别—什么是指针

  • 数组名–地址–常量
  • 指针–变量
    变量需要下地址去取值
    常量是直接进行赋值
int a = 1;
int *p = &a;

b =  *p;
/*
下地址 == 解引用
去p的地址中将存储的地址拿出来
再去拿出来的地址中取出存储的数据
*/

动态内存

动态内存
C++ new delete
C malloc free 堆上
区别:

  1. malloc出的空间申请后要判断是否为空
    new出的空间申请不成功会抛出异常 如果不让它抛出异常才可以 判空
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命名空间

解决全局变量的污染问题,解决命名冲突的问题

namespace  lee
{
	int a;
	int Sum(int a,int b)
	{return a+b;
	}
}//全局变量a和函数Sum定义在lee这个名字空间作用域下,将其隔离起来。
  • 访问方式:
  1. 通过名字空间作用域加上作用域访问符 : :
int main()
{
   //1
	cout << lee::a << endl;
	cout << lee::Sum(10, 20) << endl;
	return 0;
}
  1. 通过using指示符
int main(){
	//2
	using namespace lee;
	cout << a << endl;
	cout << Sum(1, 2) << endl;
	return 0;
}
  1. 通过using声明
    int main()
{    //3
	using lee::a;
	using lee::Sum;
	cout << a << endl;
	cout << Sum(1, 22) << endl;
	}

如果同一个项目中出现同名的名字空间作用域则将同名的名字空间作用域进行合并,合并成一个名字空间作用域,包含所有名字空间作用域中的内容

你可能感兴趣的:(笔记,C++,c++)