欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++入门必看

程序员文章站 2024-02-02 22:11:04
...

一、C++介绍

贝尔实验的本贾尼.斯特劳斯特鲁普,于1979年在分析Linux系统分布内核流量分析时,希望有一个更加模块化的工具,于是他为C语言增加了类的机制(面向对象),于1983年完了C++的第一个版本。

特点:
    1、C++完全兼容C语言的所有内容。
    2、支持面向对象的编程思想(抽象、封装、继承、多态)。
    3、支持函数、运行符重载。
    4、支持泛型编程、模板技术。
    5、技术异常处理机制。
    6、类型检查更严格。

二、第一个C++程序

1、文件扩展名

      .cpp .cc .C .cxx

2、头文件

    #include <iostream> C++中把文件操作抽象成了类。
    #include <stdio.h> 可以继续使用
    #include <cstdio>  建议这样使用,里面删除了一些可能会引起冲突的宏。

3、增加了名字空间

    using namespace std;

4、输入/输出

    cout <<  输出数据
    cin >> 输入数据
    cout/cin不需要对变量取地址,也不需要占位符,因为它们可以自动识别数据类型。
    scanf/printf/sscanf/sprintf可以继续使用。
    注意:scanf/printf是C标准库函数,cin/cout是C++标准库中的类对象,被定义在std名字空间中。

5、编译器

    g++ file.cpp -> a.out
    大多数Linux系统都需要额外安装,ubuntu系统安装命令:
        sudo apt-get update
        sudo apt-get install g++
    gcc编译器也可以继续使用,需要额外增加参数:-xc++ -lstdc++

三、名字空间

1、为什么需要名字空间

    在项目中函数名、全局变量、结构、联合、枚举、宏等,非常有可能有命名冲突,而C++之父设计名字空间就是为了解决这些命名冲突,名字空间可以把这些命名划分到不同逻辑空间里,从而解决命名冲突。

2、定义名字空间

    namespace name {
        定义变量
        定义函数
        定义类、结构、联合、枚举
    }
    名字空间可以重复,同名名字空间会自动合并。

3、使用名字空间

    使用空间中的单个标识符,空间名::标识符 使用麻烦,但安全。
    导入空间中的所有标识符,using namespace 空间名,虽然方便但依然有冲突的可能。

4、名字空间嵌套

    名字空间的内部可以再定义名字空间,这种定义方式叫名字空间嵌套。
    内层的标识符可以与外层的重名,内层的标识符会自动屏蔽外层的标识符。
    namespace n1
    {
        int num = 10;
        namespace n2
        {
            int num = 20;
            namespace n3
            {
                int num = 30;
            }
        }
    }
    多层的名字空间使用时要逐层分解。
    n1::n2::n3::num
    使用多层名字空间时为了精简长度可以取别名
    namespace n123 = n1::n2::n3;

5、匿名空间

    默认定义全局的标识符归属到匿名空间中,可以直接使用域限定符::访问匿名空间中的标识符。

四、C++的数据类型

1、布尔类型

    C++中有真正的布尔类型,不需要添加头文件,bool true false 可以直接使用。

2、结构、联合

    1、不再需要typedef,在定义结构、联合变量时可以省略 struct union关键字。
    2、成员可以是函数,成员函数中可以直接访问成员变量,而不再需要 . ->
    3、有四个隐藏的成员函数(构造、析构、拷贝构造、赋值构造)。
    4、可以对成员的访问权限进行管理,public/protected/private。
    5、结构还可以被继承。

3、枚举

     1、不再需要typedef,在定义枚举变量时可以省略 enum 关键字。
     2、使用方法与C语言基本一致,不能与整型数据混用。

4、void*

    1、在C语言中void*可以与任意类型的指针自动转换(万能指针)。
        void* p = NULL;
        int* p1 = p;
        p = p1;
    2、C++中void*不能再直接给其它类型的指针变量赋值,只能强制类型转换后才能赋值,但其它类型指针可以直接给void*类型的指针赋值。
        void* p = NULL;
        int* p1 = p; // error
    3、C++为什么要修改void*
        为了安全,另外C++可以自动识别类型,所有对万能指针的需求不再那么强烈。
    4、C++中的字符串
        1、C++中把字符串封装成了string类,实现在string头文件中,但已经被iostream包含,定义在std名字空间中。
        2、常见的字符串操作不再需要函数,可以使用运算符:
            = strcpy
            ==  strcmp
            += strcat
        3、计算字符串长度使用size成员函数。
        4、也可以与转换成C语言中的char*
            c_str() const char*
            data() char*

五、C++函数与C函数的区别

1、函数重载

    1、什么是函数重载
        C++中,在同一作用域下,形参列表不同的同名函数构成重载关系,且不会冲突。
    2、重载实现的机制
        C++代码在编译时函数的参数类型会添加函数名中,也就是说C++的函数名在编译时经历的换名的过程,借助这个方式实现了函数的重载。
        注意:由于C++和C函数的编译机制不同,所以C++代码不调用C编译器所编译出的函数。
    3、extern "C" {}
        功能是告诉C++编译器按照C语言的机制声明函数,这样C++中的代码就可以调用C编译编译出的函数了(C++目标文件与C的目标文件才能合并出可执行文件)。
    4、重载和作用域
        函数的重载关系一定发在同一作用域下,不同作用下的同名函数构成的是隐藏关系。
    5、重载函数的调用
        当调用重载函数时,编译器会根据实参的数据类型选择合适的重载函数,实参与形参匹配情况有三中:
            1、编译器找到实参与形参完全匹配的函数,编译器会生成调用指令。
            2、编译器找到多个匹配函数,但没有一个最佳的,编译器会产生二义错误。
                绝大数情况下都编译器都能找到一个最佳的匹配函数,但如果没有,编译器就会进行类型提升,这样备选函数中就可能有多个可调用的版本,然后二义性错误就产生了。
            3、编译器找不可调用的重载函数,会直接产生错误。
    6、指针类型也影响函数的重载
        C++函数的参数如果指针类型的,编译时就会在函数的末尾添加Px。
    7、如果参数是指针或引用,是否加const也会影响函数的重载。
    注意:函数重载是面向对象编程思想的多态(多种形态,根据实参情况对指令作出相应的反应)的体现,具体调用哪个版本的函数是在编译期间就确定了,所以这种也叫编译时多态。

2、默认形参

    1、在C++中函数的参数可以设置默认值,在函数调用时,如果调用者没有提供实参,则使用默认的形参。
    2、如果只一部分参数设置了默认形参,则设置默认形参的靠右排列。
    3、如果函数的声明在定义分开实现,则只需要在函数声明时设置默认形参。
    4、默认形参会影响函数的重载,慎重给重载函数设置默认形参。

3、内联函数

    1、普通函数会在调用位置生成调用(跳转)指令,当执行到调用代码时会跳转到函数所在的代码段位置执行。
    2、内联函数就是把函数编译好的二进制指令拷贝到调用位置,也就没了跳转和返回的过程。
    3、内联函数的优缺点:
        与普通函数相比,内联函数的执行速度更快。
        大量使用内联会使可执行文件变大,造成冗余。
    4、显式内联和隐式内联
        显示内联:在函数的返回值类型前加 inline (C语言也支持)
        隐式内联:结构、联合、类的成员函数会默认优化成内联函数。
        注意:是内联由编译决定(部分编译器没有实现内联的功能)。
    5、宏函数在调用时会把函数休替换到调用位置,与内联函数一样用空间来换取时,所以宏函数与内联函数的区别(优缺点)?
        1、宏函数函数仅仅是代码替换不是真正的函数,也就不会有参数传递、入栈、出栈、返回值,而且所有类型都可以使用,但不会进行类型检查,有安全隐患。
        2、内联函数是真正的函数调用,函数在调用时会进行参数类型检查、传参、压栈、出栈,也可以有返回值,还可以重载、设置默认形参,但这样就不能通用了。
    6、什么样的情况适合内联函数
        由于使用内联函数会使用可执行文件变大,增加内存开销,只有调用频繁且内容短小的函数适合作为内联函数。
        调用比较少且内容多的函数,内联后并不显著提升性能,不足以抵销特性空间带来的损失所以不适合内联。
        带有递归特性,和动态绑定特性的函数,无法实施内联,因此编译会自动忽略inline关键字。

六、C++的堆内存管理

1、在C++中使用 new/delete 管理堆内存

    相当于C语言中的malloc/free
    使用方法:
        new 类型;
        new会自动计算类型所需要的字节数,然后从堆内存申请相应字节数的内存块,并返回内存块的首地址(有类型地址)。

        delete 地址;
        释放堆内存中的内存块;
        注意:new/delete 与malloc/free不能混能,因为 new/delete 除了管理堆内存还有其它工作要做。  

2、数组的分配与释放

    new 类型[n]; 
    n表示数组的长度
    delete[] 地址;
    注意:通过 new[] 申请的内存,必须使用 delete[] 释放,与就是不能new/delete混用。

3、重复释放

    delete/delete[] 不能重复释放同一块内存。
    delete/delete[] 释放野指针的后果不确定,但释放空指针是安全的。

4、内存申请失败

    当内存申请过大,没有能满足需要的整块内存时会抛出异常"std::bad_alloc"。

new/delete 与malloc/free的区别
        new/delete              malloc/free
    身份:运算符                函数
    参数:类型(自动计算字节数)   字节数(手动计算)
    返回值:带类型的地址        void*
    申请失败:抛异常            返回NULL
    调用函数:构造/析构         无
    
    相同点:
        1、都能管理堆内存
        2、不能重复释放
        3、都可以释放空指针

七、强制类型转换

C++中有一套更安全的强制类型转换,分别是

1、静态类型转换 static_cast<目标类型>(源类型)

    源类型和目标类型只要有一个方向可以隐式转换,那么两个方向都可以做静态类型转换,如果不能则报错。

2、动态类型转换 dynamic_cast<目标类型>(源类型)

    源类型和目标类型必须同是引用或指针,且目标类型的源类型之间存在继承关系,否则报错。

3、去常类型转换 const_cast<目标类型><源类型>

    源类型和目标类型必须同是引用或指针,且目标类型的源类型只有常属性的区别,否则报错。

4、重解释类型转换 reinterpret_cast<目标类型><源类型>

    源类型和目标类型必须是指针,或者一个指针一个整数,否则报错。
本贾尼.斯特劳斯特鲁普:如果让我重新设计,它们会更长。
原因:C++之父不希望程序员使用强制类型转换,如果程序中用到了强制类型转换则说程序设计的不完善,程序员应该去优化程序的设计而不应该强制类型转换。
相关标签: C++ c++ c语言