C++ 修炼 一

学习过程中,要注意时刻比较C与C++的区别

一、C++介绍
本贾尼·斯特劳斯特卢普,于1979年4月份贝尔实验室的本贾尼博士在分析UNIX系统分布内核流量分析时,希望有一种有效的更加模块化的工具
1979年10月完成了预处理器Cpre,为c增加了类机制,也就是面向对象,1983年完成了C++的第一个版本,C with classes也就是C++
C++与C的不同点:
1、C++完全兼容C的所有内容
2、支持面向对象编程思想
3、支持运算符、函数重载
4、支持泛型编程、模板
5、支持异常处理
6、类型检查严格

二、第一个C++程序
1、文件扩展名
.cpp  .cc .C .cxx
2、编译器
g++ 、大多数系统需要额外安装,ubuntu系统下的安装命令:
sudo apt-get update   更新
sudo apt-get install g++  安装
gcc也可以继续使用但需要增加参数 -xC++ -lstdc++

gcc dome.cpp -xC++ -lstdc++
3、头文件
#include<iostream>
#include <stdio.h>  可以继续使用
#include<cstdio> 也可以使用

4、输入/输出
cout<<输出数据
cin>>输入数据
cout/cin 会自动识别类型
scanf/printf 可以继续使用
注意:cout和cin是标准库类对象,而scanf/printf是标准库函数
5、增加了名字空间
std::cout
using namespace std;
所有的标准类型、对象、函数都位于std命令空间中

sudo find / -name cstdio
vi /usr/include/c++/4.6/cstdio    std的名字空间

三、名字空间
1、为什么需要名字空间
在项目中函数名、全局变量、结构、联合、枚举、类,非常有可能名字冲突,而名字空间就对这些命名进行逻辑空间划分(不是物理单元划分,即不是 以文件划分),为了解决命名冲突      不在同一个文件有可能在同一个命名空间
2、什么是名字空间
在C++中经常使用多个独立开发的库来完成项目,由于库的作者或开发人员根本没有见过面,因此命名冲突再所难免,C++之父为防止命名冲突给C++设计一个名字空间的机制
通过使用namespace XXX把库中的变量、函数、类型、结构等包含在名字空间中,形成自己的作用域,避免名字冲突
namespace xxx
{
}//没有分号
注意:名字空间也是一种标识符,在同一作用域下不能重名
空间名与变量名不能重名
/************************************************/
int cout = 101;
int cin = 91;
std::cout <<cout<< ' ' << cin << std::endl;
/**************************************************/

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

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

4、名字空间的使用方法
:: 域限定符
空间名::标识符  //使用麻烦,但是非常安全
using namespace 空间名;把空间中定义的标识符导入到当前代码中
不建议这样使用,相当于把垃圾分类后再倒入同一个垃圾车,依然会冲突
5、无名名字空间
不属于任何名字空间中的标识符,隶属于无名名字空间。全局变量属于无名名字空间
无名名字空间中的成员使用::标识符 进行访问
如果访问被屏蔽的全局变量。 全局与局部的名字相同时,全局变量被屏蔽
6、名字空间嵌套
名字空间内部可以再定义名字空间,这叫名字空间嵌套
内层的名字空间与外层的名字空间的成员,可以重名,内层会屏蔽外层的同名标识符
多层的名字空间在使用时逐层分解
n1::n2::n3::num;
namespace n1
{
int num=1;
namespace  n2
{
int num=2;
namespace n3
{
int num=2;
}
}
}
7、可以给名字空间取别名
由于名字空间可以嵌套,这样就会导致在使用内层成员时过于麻烦,可以给名字空间取别名来解决这类问题
namespace n123 = n1::n2::n3;
using namespace n123;

四、C++的结构
1、不再需要typedef,在定义结构变量时,可以省略struct关键字
2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->,但是C的结构成员可以是函数指针
3、有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)
4、可以继承,可以设置成员的访问权限(可以面向对象,但真正主力是class)

struct Student
{
    char name[20];
    char sex;
    short age;
};
Student stu={"hehe",'m',20};
cout<<stu.name;


struct Man
{
    char id[18];
};

struct Student:public Man
{
    char name[20];
    char sex;
    short age;
};
Student stu;
strcpy(stu.id,"41270219901009");
cout << stu.id <<endl;

五、C++的联合
1、不再需要typedef,在定义结构变量时,可以省略union关键字
2、成员可以是函数(成员函数),在成员函数中可以直接访问成员变量,不需要.或->
3、有一些隐藏的成员函数(构造、析构、拷贝构造、赋值构造)

对齐与补齐没有变化
在结构中每个成员依次存储,而在联合中,所有的成员都从偏移地址0开始存储。在某一时刻,只有一个成员真正存储于该位置。

六、C++的枚举
1、定义、使用方法与C语言基本一致
2、类型检查比C语言更严格  枚举值与整型的转换不行

enum Color
{
    RED,
    YELLOW,
    BLUE,
    WHITE,
    BLACK
};

    Color r;
    // r = 0; error 类型检查更严格

七、C++的布尔类型
1、C++具有真正的布尔类型,bool是C++中的关键字,在C语言中使用布尔类型需要导入头文件stdbool.h(在C11中bool应该已经是数据类型了)
2、在C++中 true false 是关键字,而在C语言中不是
3、在C++中true false 是1字节,而在C语言中是4字节

八、C++的void*
1、C语言中void* 可以与任意类型指针 自动转换
2、C++中void* 不能给其它类型的指针直接赋值,必须强制类型转换,但其它类型的指针可以自动给void* 赋值
3、C++为什么这样修改void*?
为了更安全,所以C++类型检查更严格
C++可以自动识别类型,对万能指针的需求不再那么强烈

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

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

十、函数重载(重载、隐藏、重写覆盖)
1、函数重载:在同一作用域下,函数名相同,参数列表不同的函数,构成重载关系
/*******************************************************************/
    int sum(int a,int b)        //sum->..sumii
    {
        cout << "参数是int类型" << endl;
        return a+b;
    }
    float sum(float a,float b)
    {
        cout << "参数是float类型" << endl;
        return a+b;
    }
    double sum(double a,double b)
    {
        cout << "参数是double类型" << endl;
        return a+b;
    }
    cout << sum(1,9) << endl;             //..sumii
    cout << sum(1.5f,2.8f) << endl;//float
    cout << sum(3.14,1.33) << endl;
/*******************************************************************/
2、重载实现的机制:C++代码在编译时会把函数的参数类型添加到参数名中,借助这个方式来实现函数重载,也就是C++的函数在编译期间经历换名的过程
    sum->_Z3sumii

    注意:如果两个函数名真的一样,一定会冲突

    有两个文件,一个是cpp,执行入口main,函数声明;一个是c,函数实现;
    C++编译会优先调用C++版本的函数,没有时才会调用C版本的函数   g++ main.cpp a.c   两个都是用g++编译   可以执行
    c与C++函数能互相调用
    但是,C++代码不能调用C函数  一个用g++编译,一个用C语言编译器编译  g++ main.o a.o   会报错
    主要原因是C语言编译器编译的不会换名,C++会换名,更改函数声明的函数名字

3、extern "C" { }   加到C++中
告诉C++编译器按照C语言的方式声明函数,这样C++就可以调用C编译器编译出的函数了(C++目标文件可以与C目标文件合并生成可执行程序)
如果C想调用C++编译出的函数,需要将C++函数的定义用extern "C"包括一下

将cpp中的函数声明括起来,就可以了,不会再改变函数声明名字
    extern "C"
    {
        void func_a(int a)
    }
/********************/
a.h
-------------
#ifndef A_H
#define A_H
void func_a(int a);
#endif//A_H

main.cpp
-------------
extern "C"
{
#include "a.h"
}
/***********************/

也可以c函数作为主函数,C++作为函数实现,把C++的函数实现括起来就行了
/*********************************/
extern "C"{

void func_b(float f)
{
    printf("我是C++函数,我被调用了%f\n",f);
}

}
/*********************************/
4、重载和作用域
    函数的重载关系发生在同一作用域下,不同作用域下的同名函数,构成隐藏关系
void func(void)
{
    cout <<"我是外部函数,我被调用了" << endl;
}
namespace n1
{
    void func(void)
    {
        cout << "我是内部函数,我被调用了" << endl;
    }
}

主函数:
func();//会调用外面的func 
n1::func();//会调用内部函数

namespace n1
{
    void func(void)
    {
        cout << "我是内部函数,我被调用了" << endl;
    }
    func();//会调用它自己,递归
    ::func();//会调用外部函数
}

----------------------------------------------------
struct Man
{
    void show(void)
    {
        cout << "我是Man的秀函数" << endl;
    }
};

struct Str:public Man
{
    void show(void)
    {
        cout << "我Str的秀函数" << endl;
    }
};

Str s;
s.show();//  输出结果, 我Str的秀函数

会先调用离它最近的那个

5、重载解析
    当调用函数时,编译器根据实参的类型和形参的匹配情况,选择一个确定的重载版本,这个过程叫重载解析
    实参的类型和形参的匹配情况有三种:
    1、编译器找到与实参最佳的匹配函数,编译器将生成调用代码
    2、编译找不到匹配函数,编译器将给出错误信息
    3、编译器找到多个匹配函数,但没有一个最佳的,这种错误叫二义性
    在大多数情况下编译器都能立即找到一个最佳的调用版本,但如果没有,编译就会进行类型提升,这样备选函数中就可能具有多个可调用的版本,这样就可能产生二义性错误

void sum(short a,int b)
{
    cout << "--------------3" << endl;
}
short a = 3 , b = 7;

6、确定重载函数的三个步骤
    1、候选函数
    函数调用第一步就是确定所有可调用的函数的集合(函数名、作用域),这个集合中的函数叫候选函数
    2、选择可行函数
    从候选函数中选择一个或多个函数,选择的标准是参数个数相同,而且通过类型提升实参可被隐式转换为形参  short->int
    3、寻找最佳匹配
    优先每个参数都完全匹配的方案,其次参数完全匹配的个数,再其次是浪费内存的字节数。

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

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

int sum(int a,int b)
{
    cout << "参数是int类型" << endl;
    return a+b;
}

int sum(int a,int b,int c=100)
{

}

cout << sum(1,9) << endl;//调用时有歧义

十二、内联函数
    1、普通函数调用时是生成一个调用指令(跳转),然后当代码执行到调用位置时跳转到函数所在的代码段中执行
    2、内联函数就把函数编译好的二进制指令直接复制到函数的调用位置
    3、内联函数的优点就是提高程序的运行速度(因为没有跳转,也不需要返回),但这样会导致可执行文件增大(冗余),也就是牺牲空间来换取时间
    4、内联分为显式内联和隐式内联
        显式内联:在函数前inline(C语言C99标准也支持)
        隐式内联:结构、类中内部直接定义的成员函数,则该类型函数会被优化成内联函数
        size a.out;//代码段大小
    5、宏函数在调用时会把函数体直接替换到调用位置,与内联函数一样也是使用空间来换取时间
        面试:宏函数与内联的区别(优缺点)?
        1、身份不一样,宏函数不是真正的函数,只是代码的替换,不会有参数压栈、出栈以及返回值,也不会检查参数类型,因此所有类型都可以使用,但这样会有安全隐患
        2、内联函数是真正的函数,被调用时会进行传参,会进行压栈、出栈,可以有返回值,并会严格检查参数类型,但这样就不能通用,如果想被多种类型调用需要重载
    6、内联适用的条件
        由于内联会造成可执行文件变大,并增加内存开销,因此只有频繁调用的简单函数适合作为内联函数
        调用比较少的复杂函数,内联后并不显著提高性能,不足以抵消牺牲空间带来的损失,所以不适合内联
        带有递归特性和动态绑定特性的函数,无法实施内联,因此能执行,应该是编译器会忽略声明部分的inline关键字

十三、引用
    引用就是取艺名。
    1、引用的基本特性
        引用就是取别名,声明一个标识符为引用,就表示该标识符是另一个对象的外号
        1、引用必须初始化,不存在空引用,但有悬空引用(变量死了(函数调用结束,释放),名还留着)
        2、可以引用无名对象和临时对象,但必须使用常引用
        3、引用不能更换目标
            引用一旦完成了定义和初始化就和普通变量名一样了,它就代表了目标,一经引用终身不能再引用其它目标
        4、引用目标如果具备const属性,那么引用也必须带const属性
    2、引用型参数
        引用当作函数的参数能达到指针一样的效果,但不具备指针的危险,还比指针方便
        引用可以简单实现函数间共享变量的目的,而且是否使用引用由被调函数说了算
        引用当作函数的参数还能提高传递参数效率,指针至少还需要4字节内存,而引用只需要增加一条标识符与内存之间的绑定(映射)
    3、引用型返回值
        不要返回局部变量的引用,会造成悬空引用
        如果返回值是一个临时对象(右值),如果非要使用引用接收的话,必须使用常引用
    注意:C++中的引用是一种取别名的机制,而C语言中的指针是一种数据类型(代表内存编号的无符号整数)
练习1:实现一个C++版本的swap函数

十四、C++的内存管理
    1、new/delete C++具备申请/释放堆内存功能的运算符。相当于C语言中的malloc和free
        new 类型;会自动计算类型所需要的字节数,然后从堆分配对应字节数的内存,并返回内存的首地址(返回带类型的指针)
        /****************/
        int* p=new int;
        *p=100;
        cout<<*p<<endl;
        /*********************/

        delete 指针;会自动释放堆内存
        
        注意:new/delete与malloc/free不能混用,因为new和delete会自动调用类、结构的构造函数、析构函数
    2、数组的分配与释放
        new 类型[n];n表示数组长度,类、结构会自动调用n次构造函数
        delete[ ] 指针;通过new [ ] 分配的内存,必须通过delete[ ]释放
        new[ ] 的返回值的前4个字节中存放数组的长度  (返回值存放在数组的前4字节)
        /********************************/
        Student* stp=new Student[15];
        int* p=new int;
        *p=100;
        p=(int*)stp;
        cout<<*(p-1)<<endl;                  //输出结果:15
        /*********************************/
            
    3、重复释放
        delete/delete[ ]不能重复释放同一块内存
        delete/delete[ ] 释放野指针的后果不确定。但释放空指针是安全的
    4、内存分配失败
        当分配的内存过大,没有能满足需求的整块内存就会抛出异常 std::bad_alloc,异常。

    new/delete与C语言的malloc相同点不同点(区别)?
    
     相同点:
        1、身份 运算符 标准库函数 
        2、参数 类型(自动计算)字节数(手动计算)
        3、返回值 带类型的地址 void*地址
        4、调用构造 自动调用 不能调用构造/析构函数
        5、出错 抛异常 返回NULL

    相同点:
        1、不能重复释放
        2、可以释放NULL
        3、都能管理堆内存

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值