C++对C的扩展

C++对C的扩展

::作用域运算符

通常情况下,如果有两个同名变量,一个是全局变量,一个是局部变量,那么局部变量在其作用域中有较高的优先权, 它将屏蔽全局变量(就近原则)

代码示例

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int a = 10;
void test()
{
    int a = 20;
    cout << a << endl;
    cout << ::a << endl;
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}

namespace 命名空间

在C++中,名称可以是符号常量、变量、函数、结构、枚举、类和对象等。工程越大,名称相互冲突的可能性越大。另外使用多个厂商的类库时,也可能导致名称冲突。为了避免在大规模程序设计中,以及在程序员使用各种各样的C++库的时候,这些表示符的命名发生冲入,标准的C++引入关键字namespace,可以更好的控制标识符的作用域。

代码示例

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
namespace A
{
    int a = 10;
    namespace C
    {
        int a = 200;
    }
    void c()
    {
        cout << "A :: C" << endl;
    }
}
namespace B
{
    int a = 20;
    void c();
}
void B::c()
{
    cout << "B :: C" <<endl;
}
void test()
{
    cout << A::a << endl;
    cout << A::C::a << endl;
    cout << B::a << endl;
    A::c();
    B::c();
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}

命名空间的使用注意

  • 命名空间只能在全局范围定义

  • 命名空间内可以嵌套命名空间

  • 命名空间是开放的,可以随时把新的成员加入已有的命名空间中

  • 命名空间可以存放变量、函数、类等

  • 命名空间中的函数可以在命名空间外部定义

无名命名空间和命名空间别名

意味着命名空间的标识符只能在本文件内访问,相当于给这个标识符加上了static,使得其可以作为内部连接

#include <iostream>
#include <string>
#include <iostream>
using namespace std;

namespace
{
    int a =300;
    void func()
    {
        cout << " im 啊" <<endl;
    }
}
namespace vewrylongnamespace
{
    void func()
    {
        cout << "vewrylongnamespace" <<endl;
    }
}
void test()
{
    cout << a << endl;
    func();
    namespace aa = vewrylongnamespace;
    aa::func();
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}

using声明

  • 表示从using开始,下面的变量使用using声明的命名空间中优先获取,简化对命名空间成员访问的操作。

  • 简化的代价是容易造成命名空间的冲突。

  • using 指明使用具体的命名空间的成员。如果出现冲突则会报错,不会和全局变量冲突。

  • using遇到函数重载的时候,指定函数会对所有的函数起作用。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
namespace A
{
    int a = 10;
}
namespace vewrylongnamespace
{
    int a = 20;
}
void test()
{
    // int a = 100;
    using namespace A;
    // using A::a;
    cout << a << endl;
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
namespace B
{
    int a = 20;
    void func()
    {
        cout << "B" <<endl;
    }
    void func(int a)
    {
        cout << "func with a" <<endl;
    }
}
void test()
{
    using B::func;
    func(1);
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}

注意: 不同命名空间的同名成员使用的时候注意二义性。

语法的增强

  • 全局变量的增强检测
  • 类型检查增强,函数参数和返回值必须有类型,不可以缺省
  • 严格的类型转换
  • struct类型加强

struct的增强

  • C中定义结构体在使用的时候需要加上struct关键字, C++不需要

  • C中的结构体只能定义变量,不能定义成员函数, C++都可以

bool 类型

标准C++的bool类型有两种内建常量(true 和false)表示状态,占一个字节大小,只有两个值

C99之后才有和C++一样的bool值。

三目运算符

C语言三目运算符返回的为数据值,为右值,不能赋值

C++返回的为变量本身(引用), 为左值,可以赋值

const 关键字

C语言

  • const 修饰全局变量,内存空间在文字常量区(属于全局区,只读),不能通过num的地址修改空间内容。
  • const修饰局部变量时,变量名只读,内存在栈区(可读可写),可以通过变量的地址间接修改空间内容。

C++

  • 在C++中一个const不必创建内存空间,是否分配依赖于如何使用;而在C中一个const总是需要一块内存空间。

  • 在函数之外的const默认是内部连接,在其他文件无法访问。如果想在别的文件中使用则必须加extern。

  • C++中对于基础类型,系统不会给data开辟空间,系统会把数据放入符号表中,data可以看成真正的常量。

  • 对data取地址的时候,系统会给data开辟内存空间。

  • 使用一个变量给只读变量初始化的时候也会开辟空间,这种情况下不会将const变量放入符号表中,因此可以修改其值。

  • 对于自定义数据类型,也会开辟空间。

  • 尽量使用const替换宏。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
void test()
{
    const int data = 10;
    cout << data << endl;
    int *p = (int *)&data;
    *p = 2000;
    cout << *p << endl;
    cout << data << endl;
    int a = 10;
    const int b = a;
    int *pp = (int *)&b;
    *pp = 200;
    cout << *pp << endl;
    cout << b << endl;

}
int main(int argc, char* argv[])
{
    test();
    return 0;
}

const 替换define

  • const 有类型,可以进行编译器类型安全检查,#define没有类型,不会进行类型检查
  • const有作用域,而#define没有作用域,默认到文件末尾都是有效的

引用

引用是C++对C的重要扩充。在C/C++中,指针的作用基本都是一样的,但是在C++中增加了另外一种给函数传递地址的途径,这就是按引用传递,它也存在于其他语言中,不是C++发明的。

语法:

  • &和别名结合表示引用
  • 给某个变量取别名,就定义某个变量
  • 从上往下替换
int num = 10;
int &a = num; // 表明a是引用变量, a是num的别名

注意:

  • 引用必须初始化
  • 一旦初始化,不能随便修改

引用作用于数组

  • 使用小括号结合
  • 配合typedef
#include <iostream>
#include <string>
#include <iostream>
using namespace std;
void test()
{
    int arr[5] = {1, 2, 3, 4, 5};
    int (&a)[5] = arr;
    cout << a[0]  << a[1] << endl;
    typedef int ARR[5];
    ARR aa = {1, 2, 3, 4, 5};
    ARR &b = aa;
    cout << b[0]  << b[1] << endl;
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}

引用作为函数参数

最常见的是引用作为函数参数和返回值中,当引用被作为函数参数时,在函数内部对引用的任何修改,将对函数外部的参数产生改变。当然可以通过传递一个指针来做和传递引用相同的事情,但是引用具有更清晰的语法。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
void myswap1(int a, int b)
{
    int tmp = a;
    a = b;
    b = tmp;
}
void myswap2(int *a, int *b)
{
    int tmp = *a;
    *a = *b;
    *b = tmp;
}
void myswap3(int &a, int &b)
{
    int tmp = a;
    a = b;
    b = tmp;
}
int main(int argc, char* argv[])
{
    int a = 10, b = 20;
    myswap1(a, b);
    cout << a << " " << b << endl;
    myswap2(&a, &b);
    cout << a << " " << b << endl;
    myswap3(a, b);
    cout << a << " " << b << endl;
    return 0;
}

通过引用参数产生的效果和传地址产生的效果是一样的,引用语法更简单:

  • 函数调用的时候不需要加&符号。
  • 在被调函数中不必在参数前面加*符 引用作为其他变量的别名而存在,因此在一些场合可以代替指针。C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。

引用作为函数返回值

如果从函数中返回一个引用,必须想从函数中返回一个指针一样对待。当函数返回引用时,引用关联的内存一定要存在。

例1:

函数的返回值是引用时候,不要返回局部变量

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person(string name):name(name){}
    string name;
};
Person &func1()
{
    Person num("100");
    return num;
}
int main(int argc, char* argv[])
{
    Person &ret = func1();
    cout << ret.name << endl;
    return 0;
}
例2:

当函数返回值为左值的时候,函数的返回值类型必须为引用。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person(string name):name(name){}
    string name;
};
Person &func1()
{
    static Person num("100");
    // 函数的返回值是引用时候,不要返回局部变量
    return num;
}
void test()
{
    func1().name = "200";
}
int main(int argc, char* argv[])
{
    Person &ret = func1();
    test();
    cout << ret.name << endl;
    return 0;
}

引用的本质(C++中无法看到)

内部实现更像是常量指针

指针的引用

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
void func1(char **p)
{
    *p = (char *)calloc(1, 32);
    strcpy(*p, "hello world");
}
void func2(char *&p)
{
    p = (char *)calloc(1, 32);
    strcpy(p, "hello world");
}
void test()
{
    char *p = nullptr;
    func1(&p);
    cout << p << endl;
    free(p);
    char *p2 = nullptr;
    func2(p2);
    cout << p2 << endl;
    free(p2);
}
int main(int argc, char* argv[])
{
    test();
    return 0;
}

常引用

常量引用主要是应用做函数参数,尤其是在类的拷贝/复制构造函数。将函数的形参定义为常量的好处:引用不产生新的变量,减少形参与实参传递时候的开销。由于阴影可能导出实参随形参的改变而改变,将其定义为常量引用可以消除这种副作用。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
class Person
{
public:
    Person(string name):name(name){}
    string name;
};
void test(const Person &p)
{
    cout << p.name << endl;
}
int main(int argc, char* argv[])
{
    Person p("aaa");
    test(p);
    return 0;
}

注意: 字面量不能赋给引用,但是可以赋值给const引用,const 修饰的引用不能被修改

内联函数

C++从C中继承的重要特征就是效率。假如C++效率明显低于C的效率,那么就会有很大的一批程序员不会去使用C++。在C中我们经常吧一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由是为了 执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。

但是C++出现之后,使用预处理宏会出现两个问题:

  1. 在C中也会出现的问题,宏看起来像一个函数调用,但是会出现一些难以发现的错误。
  2. C++特有问题, 预处理器不允许访问类的成员,也就是说预处理器宏不能用做类的成员函数。

为了保持预处理宏的效率又增加安全性,而且还能像一般程序员函数那样可以在类里访问自如,C++引入了内联函数(inline)

内联函数继承了宏函数的效率,没有函数调用时候的开销,然后又可以像普通函数那样,可以进行参数返回值的类型安全检查,又可以作为成员函数

内联函数和编译器

C++中,预定义宏函数是使用内联函数来实现的,而内联函数本身就是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同的是内联函数会在适当的地方像预定义宏一样展开,所以不需要调用开销。因此应该不适用宏,而使用内联函数。在普通函数前面加上inline使得其编程内联函数。但是必须注意,函数体和声明需要结合在一起,否则编译器会将它作为普通函数来对待。

类内部的内联函数

类内部函数时inline不是必须的,任何类内部定义的简单函数都可能会自动变为内联函数

内联函数限制
  • 不能存在任何形式的循环语句
  • 不能存在过多的判断语句
  • 函数体不能过于庞大,不能对函数进行取址操作

内联仅仅是给编译器一个建议,编译器不一定会接受这个建议,如果你没有将函数声明为内联函数,那么编译器也有可能将函数内联编译。

缺省参数和占位参数

缺省参数、

C++在声明函数原型的时候可以为一个或者多个参数指定默认(缺省)参数值,当函数调用的时候如果没有指定这个值,编译器会自动用默认值代替。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int add(int x= 10, int y = 20)
{
    return x+y;
}
int main(int argc, char* argv[])
{
    cout << add() << endl;
    cout << add(50) << endl;
    cout << add(50, 60) << endl;
    return 0;
}

注意:

  • 函数默认参数从左到右,如果一个参数设置了默认参数,那么这个参数之后的参数都必须 设置默认参数

  • 如果函数声明和函数定义分开写,函数声明和函数定义不能同时设置默认类型参数

  • 如果函数声明和函数定义分开写,函数定义处的缺省参数时无效的,建议在声明处给默认参数

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int add(int x = 10, int y = 10);
int main(int argc, char* argv[])
{
    cout << add() << endl;
    cout << add(50) << endl;
    cout << add(50, 60) << endl;
    return 0;
}
int add(int x, int y)
{
    return x+y;
}

占位参数

C++在声明函数时,可以设置占位参数。占位参数只有参数类型声明,而没有参数名称声明。一般情况下,在函数体内部无法使用占位参数。

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int add(int, int, int);
int main(int argc, char* argv[])
{
    cout << add(50, 60, 100) << endl;
    return 0;
}
int add(int x, int y, int c)
{
    return x + y + c;
}

同样的,如果实现也不写参数名,则该参数必须传递,但是无法使用。

重载后置++的时候会用占位参数。

函数重载(overlaod)

同一个函数名在不同场景下可以具有不同的含义,重载使得函数名称使用更方便。

实现函数重载条件:同一个作用域,参数个数不同,参数类型不同,参数顺序不同

#include <iostream>
#include <string>
#include <iostream>
using namespace std;
int add(int, int, int);
int add(int, int);
int main(int argc, char* argv[])
{
    cout << add(50, 60, 100) << endl;
    return 0;
}
int add(int x, int y, int c)
{
    return x + y + c;
}
int add(int x, int y)
{
    return x + y;
}

注意:

  • 函数的返回值类型不能作为函数重载的依据

  • 函数重载和默认参数在一起的时候会产生二义性问题

为什么返回值不作为重载条件呢?

因为我们在写程序的时候可以忽略函数的返回值,那么直接调用函数的时候编译器无法根据返回值类型来确定调用哪个函数,所以C++中禁止使用返回值类型做重载条件。

编译器在编译重载函数的时候会将重载函数名称修改成自己的函数名称。但是编译器没有统一标准,因此不同的编译器会产生不同的函数名称。

C++和C混合编程

c函数:void myfunc(){},被编译成myfunc

C++函数 void myfunc(){},被编译成:z6myfuncv

由于C++需要支持函数重载,所以C和C++中对同一个函数经过编译之后生成的函数名称是不一样的,这就导致了一个问题,如果在C++中调用一个使用C语言编写模块中的某个函数,那么C++是根据C++的名称修饰方式去查找并且连接这个函数的,这样会发生连接错误。

在C++中包含C库的时候需要将头文件使用extern “C”{}包含起来,这样对应的代码会用C编译器去编译,而不适用C++的方式。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

turbolove

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值