C++笔记-Day1(名字空间,函数重载,引用,内存管理)

C++

C++介绍

C++之父 Bjarne Stroustrup于1979年4月份贝尔实验室的bjarne博士在分析UNIX系统分布内核流量分析时,希望有一种有效的更加模块化的工具

1979年10月完成了于处理器Cpre,为C增加了类机制,也就是面向对象.1983年完成了C++的第一个版本,C With Classes 也就是C++

C++于C的不同点:

  • C++完全兼容C的所有内容
  • 支持面向对象的编程思想
  • 支持运算符,函数重载
  • 支持泛型编程,模板
  • 支持异常处理
  • 类型检查严格

第一个C++程序

  1. 文件扩展名

    .cpp/.cc/.C/.cxx

  2. 编译器

    g++ 大多数系统需要额外安装ubuntu下的命令:

    sudo apt-get update
    sudo apt-get install g++
    

    gcc也可以继续使用,但需要增加参数 -xC++ -lstdc++

  3. 头文件

    #include <iostream>
    #include <stdio.h>可以继续使用
    #include <cstdio>也可以使用

  4. 输入,输出

    • cout << 输出数据
    • cin >> 输入数据
    • cin/cout 会自动识别类型
    • 注意cin/cout是类对象而scanf/printf是标准库函数
  5. 增加了名字空间

    std::cout
    using namespace std;
    所有标准类型,对象,函数都位于std命令空间中

名字空间

  1. 为什么需要名字空间

    在项目中,函数名,全局变量,结构,联合,枚举,类,非常有可能名字冲突,而名字空间就对这些命名进行逻辑空间划分(不是物理单元划分),为了解决命名冲突

  2. 什么是名字空间

    在C++中,经常使用多个独立开发的库来完成项目,由于库的开发人员不同,因此命名冲突在所难免,Bjarne为防止命名冲突,给C++设计了一个名字空间的机制

    通过使用namespace XXX把库中的变量,函数,类型,结构等包含在名字空间,形成自己的作用域,避免名字冲突

    namespace xxx
    {
    
    }//无 ";"
    

    名字空间也是一种标识符,在同一作用域下不能重名

  3. 同名的名字空间可以自动合并(为了让声明和定义可以分开写)

    同名的名字空间中如果有重名依然会命名冲突

  4. 名字空间的使用方法

    ::域限定符

    空间名::标识符 //使用麻烦但是非常安全

    using namespace 空间名 把空间中定义的标识符导入到当前代码中//不建议这样使用,有冲突隐患

  5. 无名名字空间

    不属于任何名字空间中的标识符,隶属于无名名字空间,使用空的域限定符进行访问::标识符

    如访问被屏蔽了的全局变量

  6. 名字空间的嵌套

    名字空间内部可以再定义名字空间,内外层之间的成员可以重名,内层会屏蔽外层

    多层的名字空间再使用时逐层分解n1::n2::n3::num

    namespace n1
    {
        int num = 1;
        namespace n2
        {
            int num = 2;
            namespace n3
            {
                int num = 3;
            }
        }
    }
    
  7. 可以给名字空间取别名

    由于名字空间可以嵌套,这样就会导致在使用内层成员时过于麻烦,可以给名字空间取别名
    namespace n123 = n1::n2::n3;

C++的结构

  1. 不在需要 typedef,在定义结构变量时,可以省略struct关键字
  2. 成员可以是函数(成员函数),在成员函数中,可以直接访问成员变量,不需要./->,但是C的结构体可以使用函数指针
  3. 有隐藏成员函数(构造,析构函数,拷贝构造,复制构造)
  4. 可以继承,可以设置成员的访问权限(面向对象)

C++的联合

  1. 不在需要 typedef,在定义结构变量时,可以省略union关键字
  2. 成员可以是函数(成员函数),在成员函数中,可以直接访问成员变量,不需要./->
  3. 有隐藏成员函数(构造,析构函数,拷贝构造,复制构造)

C++的枚举

  1. 定义,使用方法与C语言基本一致,
  2. 类型检查比C语言更严格

C++的布尔类型

  1. C++中具有真正的布尔类型,bool是C++中的关键字,而C语言中不是(需要stdbool.h)
  2. true/false在C++中是关键字,而在C语言中不是
  3. true/false在C++中是1字节而在C语言中是4字节

C++的void*

  1. C语言中void*可以与任意类型的指针自动转换
  2. C++中,void*不能给其他类型的指针直接赋值,必须强制类型转换,但是其他类型可以自动转换成void*
  3. C++为什么修改void*
    • 为了更安全,所以C++类型检查更严格
    • C++可以自动是被类型,对万能指针需求不在那么强烈

操作符别名

某些特殊语言的键盘上没有~,&等符号,所以C++标准委员会为了让C++更具竞争力,为这些符号定义了一些别名,让这些小语种也可以愉快的编写C++代码了

  • and &&
  • ro ||
  • not !
  • <% {
  • %> }
  • %: #

函数重载

  1. 函数重载

    在同一作用域下,函数名相同,参数列表不同的函数,构成重载的关系

  2. 重载实现的机制

    C++代码在编译时,会把函数的参数类型添加到函数名中,借助这个方式来实现函数的重载,也就是说,C++的函数在编译期间经历了换名的过程

    因此C++代码不能调用C函数(C语言编译器编译出的函数)

    注意 如果两个函数名一样,一定会造成冲突

  3. extern "C"{C++中对C函数的声明}

    告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数了(C++目标文件可以与C目标文件合并生成可执行文件)

    如果C想调用C++编译出的函数,需要将C++函数定义用extern "C"{}

  4. 重载和作用域

    函数的重载关系发生在同一作用域下,不同作用域下的同名函数发生的是隐藏

  5. 重载的解析
    当调用函数时,编译器根据实参的类型和形参的匹配情况,选择一个确定的重载版本,这个过程叫做重载解析

    实参的类型和形参的匹配情况(3种):

    1. 编译器找到与实参最佳的匹配函数,编译器将生成调用代码
    2. 编译器找不到匹配函数,编译器将给出错误信息
    3. 编译器找到好几个能够匹配的函数,但是没有一个是最佳的这种错误叫做二义性

    在大多数情况下,编译器都能立即找到一个最佳的调用版本,如果没有,编译器就会进行类型提升,这样被选函数种就可能具有多个可调用的版本,这样就可能产生二义性错误

  6. 确定重载函数的3个步骤

    1. 候选函数

      函数调用的第一步就是确定可调用的候选函数的集合(函数名,作用域)

    2. 选择可行函数

      从候选函数中选择一个或多个函数(参数个数相同,而且通过类型提升实参可被隐式转换成形参)

    3. 寻找最佳匹配

      优选每个参数都完全匹配的方案,其次参数完全匹配的个数,在其次浪费内存的字节数

  7. 指针类型也会对函数的重载造成影响
    C++函数在编译时形参如果类型是指针类型,编译时函数名中会追加Px

默认形参

  • 在C++中函数的形参可以设置默认值,调用函数时如果没有提供实参,则使用默认形参
  • 如果形参中只有一部分设置了默认形参,则必须靠右排列
  • 函数的默认形参是在编译阶段确定的,因此只能使用常量/常量表达式/全局变量作为默认值
  • 如果函数的声明和定义需要分开,那么默认形参只设置在声明中
  • 默认形参会对函数重载造成影响,设置默认形参时一定要慎重

内联函数

  1. 普通函数调用时是生成调用指令(跳转指令),然后当代码执行到调用位置时跳转到函数所在的代码段中执行
  2. 内联函数就会把编译好的二进制指令直接复制到函数的调用位置
  3. 内联函数的优点就是能够提高函数的运行速度(没有跳转,没有返回),但这样会导致可执行文件增大(冗余),牺牲空间换取时间
  4. 内联分为显式内联和隐式内联
    • 显式内联:在函数前 inline(C语言C99标准也支持)
    • 隐式内联:如果结构,类中直接定义的成员函数,则该类型函数自动优化成内联函数
  5. 宏函数在调用时会把函数体直接展开替换到调用位置,与内联类似

宏函数和内联函数的区别(优缺点)?

宏函数不是真正的函数,只是代码的替换,不会有参数的压栈出栈操作,也不会进行参数的类型检查,因此所有类型都能使用

内联函数是真正的函数,函数调用时会传参,压栈,出栈,返回,并严格检查参数类型,这样就不能通用,想被多种类型调用,需要重载

  1. 内联适用的条件
  • 由于内联会造成可执行文件变大,并且增加内存开销,因此只有频繁调用的简单函数适合作为内联函数
  • 调用比较少的,比较复杂的函数不适合内联,内联后并不能显著提高性能,不足以抵消牺牲空间带来的损失
  • 带有递归特性和动态绑定特性的函数无法实施内联,因此编译器会忽略声明部分的inline关键字

引用

引用就是取艺名

  1. 引用的基本特性

    引用就是取别名,声明一个标识符为引用,就表示该标识符是另一个对象的外号

    1. 引用必须初始化,没有空的引用,悬空引用(变量死了,名还留着,函数内返回的局部变量的引用)
    2. 可以引用无名对象,临时对象,但必须使用常引用
      • const int & xiu = 18;//int const &
    3. 引用不能更换目标
      • 引用一旦完成了初始化,就和变量一样,它就代表了目标,而不是别名了,一经引用,终身不能引用其他目标
  2. 引用型参数

    • 引用当作函数的参数,达到指针同样的效果,但不具备指针的危险,还比指针方便
    • 引用可以非常简单实现函数减共享变量的目的,而且是否使用引用由被调函数说了算
    • 引用当作函数参数还能提高传参效率,指针至少还需要4字节,内存而引用只需要增加一条标识符与内存地址之间的映射
  3. 引用型返回值

    • 不要返回局部变量的引用,会造成悬空引用
    • 如果返回值是一个临时对象(右值),如果非要用引用来接,使用常引用const int& =
  4. C++中的引用是一种取别名的机制,而C语言中的指针是一种数据类型(代表内存编号的无符号整数)

练习1 实现一个C++版本的swap函数

C++的内存管理

  1. new/delete

    • C++中具备申请和释放堆内存功能的运算符,相当于C语言中的malloc和free
    • new 类型; 会自动计算类型所需要的字节数,然后从堆中分配对应字节数的内存,返回内存的首地址(具备类型)
    • delete 指针; 会自动释放堆内存

    注意 new/deletemalloc/free不能混用,因为new和delete会自动调用类,结构的构造函数,析构函数

  2. 数组的分配与释放

    • new 类型[n] n表示数组长度,会自动调用n次构造函数
    • delete[] 指针 通过new[]分配的内存,必须通过
    • new[] 返回值前4个字节中存放了数组的长度
  3. 重复释放

    • delete/delete[]不能重复释放同一块内存
    • delete/delete[]释放野指针的后果不确定,但它可以释放NULL指针
  4. 内存分配失败

    • 当分配的内存过大,没有能需求的整块内存就会抛出异常std::bad_alloc
    • new/delete与C语言的malloc相同点不同点(区别)?
    不同
    身份运算符标准库函数
    参数类型(自动计算)字节数(手动计算)
    返回值带类型的地址void*地址
    调用构造自动调用不能调用构造/析构函数
    出错抛出异常返回NULL
    相同
    1. 都能管理堆内存
    2. 不能重复释放
    3. 可以释放NULL

    注意 在C++中,尽量使用 引用,new/delete


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值