c和c++区别(一)——默认值、inline、cosnt和引用

一、带有默认值的函数
1、c的版本
   c:c89(我们现在使用的版本,不支持默认值)、c99(可以支持默认值)、c11(2011年发行的版本)
2、c++版本   
   c++:c89          c++99(我们现在使用的版本,支持默认值)   c++11  

3、代码示例
1)int sum(int a, int b);
     int sum(int a, int b);
     ...
像这样声明10遍都没有关系,因为声明并不产生符号。
2)但是如果一个文件中定义两次,那就有问题了,因为会产生两个同样的符号,会产生符号的重定义
     error:重复定义错误
     问题:重复定义是在哪个阶段报错的。
     a.c
{
    int sum(int a, int b)
{
    return a+b;
}

   int sum(int a, int b)
{
   return a+b;
}
}
3)默认值的参数传递是遵循从右往左的顺序,不能跳跃。
    int sum(int a,  int b=20)           //正确
{
     return a+b;
}

4) int sum(int a=10, int b)                //错误,b还没有默认值,直接就给a赋值了
{
    return a+b;
}

5)int sum(int a,  int b=20);
     int  sum(int a=10,int b);
     正确,因为编译器从上往下扫描的时候,在给a赋予默认值的时候,b已经赋值过了。 
6)声明处给定义默认值,定义处不给,只要遵循从上往下扫描,默认值从右往左的赋值顺序就可以了
   int sum(int a,  int b=20);
   int sum(int a,  int  b)
{
   return a+b;
}  

7)但是默认值不能重复定义
   int sum(int a,int b=10);
   int sum(int a, int b=10);
//错误,因为默认值只能给一次,就算是一样的,也不行。

重点:给默认值的时候,只要遵守从上往下扫描的时候,遵循从右往左的顺序就可以了,无所谓是在声明还是在定义中。


二、inline
1、源文件a.c
    int sum(int a,int b);
    int main()
{
    int a=10;
    int b=20;
    int ret =sum(a,b);
    return 0;
}

b.h
int sum(int a,int b)
{
   return a+b;
}
       在源文件中,因为引用了外部符号,所以在链接的过程中,合并符号表之后,要进行符号的解析。编译是单独编译的,在编译a.c的时候还不知道sum的存在,所以sum作为一个外部符号是 *UND*,链接合并符号表之后,我们可以进行符号解析,在符号表中找到sum的定义,它在b.h中的.text段中,如果找不到定义,就会报错。

2、如果在b.h 的文件中把sum用inline修饰会怎么样呢,即源代码变成了下面的样子 
源文件a.c
    int sum(int a,int b);
    int main()
{
    int a=10;
    int b=20;
    int ret =sum(a,b);
    return 0;
}

b.h
inline int sum(int a,int b)
{
   return a+b;
}

       这样链接的时候就会报错,因为inline相当于一个安全的宏,它在调用的点直接展开它,(这里就是用return a+b替换)它不产生符号,所以链接的时候a.c中引用的地方尝试找到sum的定义就找不到了,就会报错。
3、那inline修饰的函数和宏的区别是什么呢?
       宏是在预编译的阶段就处理了,而内联函数是在编译阶段,内联更安全是因为在编译阶段可以进行类型检查,预处理的时候宏当然是不会检查了。

4、内联函数和普通函数的区别
    1) 内联函数没有栈帧的开辟和回退,而普通函数却有。
     所以,如果函数调用开销>函数执行的开销,则使用内联函数;如果函数调用开销<函数执行的开销,则使用普通函数;
   2)内联函数不产生符号,所以无法再其他文件中声明再使用。它一写在头文件中,可以通过包含它所在的头文件使用它。并且不会出现重定义,也不会发生任何问题,因为头文件在预处理的时候就会把包含进去的东西展开,然后在编译阶段把调用点进行指令替换。如果写在.c的源文件中,则只能在那一个文件中使用。如果是普通的函数,在头文件中实现,而且被几个文件一起引用,则会发生链接错误,因为,它们在各自的文件中都产生了同样的符号。
3)内联函数只在本文件中有效
注意:内联函数只是一个建议,最后采不采用还是看编译器本身的决定,像是如果是给一个递归函数修饰inline,编译器是不会采用的,并且,它只有在release版本中才会有效,在debug 版本中是不会有效的,不方便调试。
5、内联函数和static函数的区别
1)作用域相同:两个函数都是只在本文件可见。
2)static函数产生符号,但是内联函数不产生符号。
3)static函数有栈帧的开辟和回退。

6、static函数和普通函数的区别
1)作用域,static函数只在本文件中可用,普通函数整个工程都可见
2)符号,我们链接器处理的是全局的符号,普通函数值产生global的符号,而静态函数产生的符号是local的,链接器都不看local的符号。
 
 三、函数的重载
1、定义:名字相同,参数不同。但是如果说是只有返回值不同,其他都是相同的,则是错的,因为我们的返回值并不参与我们的命名规则,在c中不支持重载,因为它的命名只和函数名有关。
.cpp
bool compare(int a,int b)
{
    cout<<"compare(int,int)"<<endl;
    return a>b;
}

bool compare(double a,double b)
{
     cout<<"compare(double,double)"<<endl;
     return a>b;
}

bool compare(char * a,char *b)
{
     cout<<"compare(char *,char *)"<<endl;
     return strcmp(a,b)>0?true:false;
}

int main()
{
     compare(10,20);
     compare(10.5,20.5);
     compare("hello","world");
}
2、函数重载指的是要在相同的作用域,一个在局部,一个在全局作用域不行
bool compare(int a,int b)
{
    cout<<"compare(int,int)"<<endl;
    return a>b;
}

bool compare(double a,double b)
{
     cout<<"compare(double,double)"<<endl;
     return a>b;
}

bool compare(char * a,char *b)
{
     cout<<"compare(char *,char *)"<<endl;
     return strcmp(a,b)>0?true:false;
}

int main()
{
     //如果我们加上一个声明
     bool compare(int ,int);   //编译会报错,因为这个在局部作用域,就近原则,我们局部作用域会掩盖住我们
                                       //全局的作用域,下面的三个调用都会使用参数为int类型的这个函数声明的函数定义。
     compare(10,20);
     compare(10.5,20.5);  //报警告,说要从double类型转成int类型
     compare("hello","world");  //报错,无法从const char*转化为int类型。
}

3、类型转换
bool compare(int a,int b)
{
    cout<<"compare(int,int)"<<endl;
    return a>b;
}

bool compare(float a,float b)
{
     cout<<"compare(double,double)"<<endl;
     return a>b;
}

bool compare(char * a,char *b)
{
     cout<<"compare(char *,char *)"<<endl;
     return strcmp(a,b)>0?true:false;
}

int main()
{
     compare(10,20);
     compare(10.5,20.5); // 会报错,因为double和int以及float都不匹配,但是转换也不知道是转int还是float
     compare("hello","world");
}

我们可以看一下类型转换的图,再做两个题了解一下这个隐式转换。


       上图中有箭头的表示编译器没有我们干涉的情况自己优先转化的方向。如果没有的都是强转的,像我们刚刚例子,编译器不知道double是向int还是像float转。
1)unsigned int a=1;
      char =-1;
     char c=a>b?'a':'b';
     cout<<c<<endl;
结果是:b
原因:我们可以看图,首先在做算术运算的时候,我们要将float、short和char进行无条件转化,然后我们b就转化成int了,这个时候我们的a是unsigned,所以还需要将b继续转化成unsigned int 这样两个类型就一样了,-1
转化成有符号类型就是全1,也就是unsigned int中能表示的最大值,所以计算的时候计算机任务b要大一些。


2)unsigned short a=1;
     char b=-1;
     char c=a>b?'a':'b';
     cout<<c<<endl;
结果是:a;
原因:因为我们先要将两个都无条件的都转化为int类型,然后两个类型匹配了,就不需要再转了,所以就是int类型,所以a>b;

4、const
在c中:

const int a=10;
a =11;  //错const 修饰int 不能修改

const int a;   //对,c中不是必须初始化,但是错过了这次机会,之后也不能再给值了。

const修饰的是常变量,不是常量,它不能做数组下标,而且也可以不初始化。
const修饰的常变量和普通变量的区别,常变量不能作为左值出现。
const int a=10;
int *p =(int *)&a;
*p=20;

它这样也是正确,因为a只是不能作为左值,不能拿常变量的名字作为左值修改,不是通过a修改的,那是怎么用还是怎么用,只是注意把a的地址赋值给p的时候需要强转一下,不强转类型不匹配还是会报错。

常变量和普通变量的编译方式一模一样,普通变量怎么取值,常变量怎么取值,普通变量怎么修改,常变量就怎么修改。只是不能拿常变量的值作为左值,其他都行。

c中如果想用外部的常变量,和调用普通变量一模一样,extern 声明一下就可以了,注意是声明不是定义,定义的话就会发生重定义了。

c++中
const int a =10;
const修饰的量,都是常量,必须初始化。
const编译规则在c++中和宏的编译规则非常像, 编译的时候在所有使用常量值的地方替换成常量的初始值,,可以作为数组下标。
const int a=10;
int *p=(int*)&a;
*p=30;
cout<<a<<*p<<endl;  //输出结果: 10     30
原因分析:因为在编译的时候,在所有用常量a的值的地方都进行了替换,所以cout后面的a不是a了,是10;
但是&a却没有替换,这是因为我们说的替换是说在使用a的常量值的地方,它取的是地址,用的不是a的初始化的常量值,当然不能替换。

当const 在初始化的时候引用一个不明确的值的时候,就会转化为常变量。
int b=10;
const int a=b;//变成常变量了
int c[a]; //错,因为a已经是常变量了,不能做下标。
int *p=(int *)&a;
*p=30;
cout<<a<<*p<<endl;  //输出结果   30  30
现在和c语言中的常变量一样了。

c++中,有const默认产生local符号,非要可以让其他文件访问,就在定义的地方加extern。

面试题:
float a=10.5;
int b1=(int)a;  //输出的是10,因为将a强制类型转化为int了
int b2=(int)&a; //用十进制输出了a的地址
int b3=(int &)a;   //10;

前两个没有什么好解释,b3的值为什么是10呢,我转到汇编代码的时候发现,我们给b3指令赋值的语句和正常的行的int b=a,是一样的,这说明编译器并没有搭理那个引用,我们也可以这么理解,我们将a转换成一个引用的时候,也就是这个整体底层用了指针实现,我么这个整体是不是代表a的引用,有引用出现的时候底层实现又自带解引用,右边的整理又是a,那其实是没必要经过中间周折的过程,可以直接将a值赋值给b。


int a=10;
int &b=a;
int *p=a;
cout<<hex<<&a<<" "<<&b<<&p<<endl;
           十六进制输出 & a  和&b 的值是一样的,都是输出的a的地址,这是因为出现b的的地方自带解引用,出现b解引用到a,然后又取地址,其实就是取的a的地址。
cout<<hex<<&a<<" "<<&b<<&*p<<endl;
三个指向了同一个地址,所以对其中任意一个的修改,其他两个都可以看到。
b=20;  //a和*p 都变成了20
*p=10;//b和a都变成了20;



int  array[10]={0};
int *p1=&array[0];
int  (*p2)[10]=&array;
int  (&p3)=array;
cout<<sizeof(array)<<sizeof(p1)<<sizeof(p2)<<sizeof(p3)<<endl;
输出:     40                     4                  4                   40
引用出现的地方自动解引用到array了。

如果是用数组引用传实参过去,测出来也是40;

void func(int(&p)[10])
{
   cout<<sizeof(p)<<endl;  //输出40;
}

int main()
{
     int array[10]={0};
     int (&q)[10]=array;
     func(q);
}

引用的话它的解引用操作是底层实现的,一出现这个引用,它直接到array,所以穿参数过去求出来还是40。

引用需要注意的点:
1)必须初始化   int &a
2)初始化的值必须能取地址  int &a =10 //不行
3)引用不能改变 int& a=b     & a=c;//直接&a就不叫引用了,叫取地址了,类型加&才是引用。
4)访问引用变量就是访问它所引用的内存。不能访问到a自身的内存 它自带解引用,一出现a就解引用,所以出现a就到b上了,也类似于*p 然后再取地址,就是b的内存;

引用比指针更方便更安全,因为指针不知道它是否指向了有效内存,但是引用初始化的时候就要求了,所以一定有效。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值