C++对C的扩展

来源:互联网 发布:淘宝同行低价恶意竞争 编辑:程序博客网 时间:2024/05/23 19:11

C++对C的扩展

  • 1.感官认识

1.最简单的C++程序

#include <iostream>using namespace std;int main(int argc, char **argv){    cout<<"hello world\n"<<endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8

2.类和对象初体验

#include <iostream>using namespace std;class Circle{public :    float r;    float s;    float l;public:    float getRadius()    {        return r;    }    void setRadius(float m_r)    {        r = m_r;    }    float getS()    {        s = 3.14*r*r;        return s;    }    float getL()    {        l = 2*3.14*r;        return l;    }};int main(int argc, char **argv){    float r = 0;    Circle c1;    cout<<"input the radius of the circle:"<<endl;    cin>>r;    c1.setRadius(r);    cout<<"the S is :"<<c1.getS()<<endl;    cout<<"the L is :"<<c1.getL()<<endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

3.类里面必须要有成员函数

#include <iostream>using namespace std;//c++的命名空间class circle{public:    double r;    double pi = 3.1415926;    double area = pi*r*r;};int main(){    circle pi;    cout << "input area" << endl;    cin >> pi.r;    cout << pi.area << endl;    //乱码    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25

说明:在使用类定义对象的时候,编译器会根据类的成员变量的初始值初始化对象的成员变量。就算更改了某个成员变量的值,并不会更改与之关联的成员变量的值。 
如果要程序执行相关语句,必须通过调用成员函数的方式得以实现。

2.与C语言的关系

1.C语言是在实践的过程中逐步完善起来的

  • 没有深思熟虑的设计过程
  • 使用时存在很多“灰色地带”
  • 残留过多低级语言的特征
  • 直接利用指针进行内存操作

2.C语言的目标是高效 
最终程序执行效率的高效

3.当面向过程方法论暴露越来越多的缺陷的时候,业界开始考虑在工程项目中引入面向对象的设计方法,而第一个需要解决的问题就是:高效的面向对象语言,并且能够兼容已经存在的代码。 
C语言 + 面向对象方法论===》Objective C /C++

4.C语言和C++并不是对立的竞争关系

  • C++是C语言的加强,是一种更好的C语言
  • C++是以C语言为基础的,并且完全兼容C语言的特性

5.学习C++并不会影响原有的C语言知识,相反会加深对C的认知,学习C++可以接触到更多的软件设计方法,并带来更多的机会。

  • C++是一种更强大的C,通过学习C++能够掌握更多的软件设计方法
  • C++是Java/C#/D等现代开发语言的基础,学习C++后能够快速掌握这些语言
  • C++是各大知名软件企业挑选人才的标准之一

3.发展历史

1.面向过程的结构化程序设计方法

  • 设计思路 
    • 自顶向下、逐步求精。采用模块分解与功能抽象,自顶向下、分而治之。
  • 程序结构: 
    • 按功能划分为若干个基本模块,形成一个树状结构。
    • 各模块间的关系尽可能简单,功能上相对独立;每一模块内部均是由顺序、选择和循环三种基本结构组成。
    • 其模块化实现的具体方法是使用子程序。
  • 优点: 
    有效地将一个较复杂的程序系统设计任务分解成许多易于控制和处理的子任务,便于开发和维护。
  • 缺点:可重用性差、数据安全性差、难以开发大型软件和图形界面的应用软件 
    • 把数据和处理数据的过程分离为相互独立的实体。
    • 当数据结构改变时,所有相关的处理过程都要进行相应的修改。
    • 每一种相对于老问题的新方法都要带来额外的开销。
    • 图形用户界面的应用程序,很难用过程来描述和实现,开发和维护也都很困难。

2.面向对象的方法

  • 将数据及对数据的操作方法封装在一起,作为一个相互依存、不可分离的整体——对象。
  • 对同类型对象抽象出其共性,形成类。
  • 类通过一个简单的外部接口,与外界发生关系。
  • 对象与对象之间通过消息进行通信。

3.面向对象的基本概念

  • 对象 
    • 一般意义上的对象: 
      • 是现实世界中一个实际存在的事物。
      • 可以是有形的(比如一辆汽车),也可以是无形的(比如一项计划)。
      • 是构成世界的一个独立单位,具有 
        • 静态特征:可以用某种数据来描述
        • 动态特征:对象所表现的行为或具有的功能
    • 面向对象方法中的对象: 
      • 是系统中用来描述客观事物的一个实体,它是用来构成系统的一个基本单位。对象由一组属性和一组行为构成。
      • 属性:用来描述对象静态特征的数据项。
      • 行为:用来描述对象动态特征的操作序列。
  • 类 
    • 分类——人类通常的思维方法
    • 分类所依据的原则——抽象 
      • 忽略事物的非本质特征,只注意那些与当前目标有关的本质特征,从而找出事物的共性,把具有共同性质的事物划分为一类,得出一个抽象的概念。
      • 例如,石头、树木、汽车、房屋等都是人们在长期的生产和生活实践中抽象出的概念。
    • 面向对象方法中的”类” 
      • 具有相同属性和服务的一组对象的集合
      • 为属于该类的全部对象提供了抽象的描述,包括属性和行为两个主要部分。
      • 类与对象的关系: 
        犹如模具与铸件之间的关系,一个属于某类的对象称为该类的一个实例。
  • 封装 
    也就是把客观事物封装成抽象的类,并且类可以把自己的数据和方法只让可信的类或者对象操作,对不可信的进行信息隐藏。 
    • 把对象的属性和服务结合成一个独立的系统单元。
    • 尽可能隐蔽对象的内部细节。对外形成一个边界(或者说一道屏障),只保留有限的对外接口使之与外部发生联系。
    • 继承对于软件复用有着重要意义,是面向对象技术能够提高软件开发效率的重要原因之一。
    • 定义:特殊类的对象拥有其一般类的全部属性与服务,称作特殊类对一般类的继承。
    • 例如:将轮船作为一个一般类,客轮便是一个特殊类。
  • 多态 
    多态是指在一般类中定义的属性或行为,被特殊类继承之后,可以具有不同的数据类型或表现出不同的行为。这使得同一个属性或行为在一般类及其各个特殊类中具有不同的语义。

  • 面向对象的软件工程 
    面向对象的软件工程是面向对象方法在软件工程领域的全面应用。它包括:

    • 面向对象的分析(OOA)
    • 面向对象的设计(OOD)
    • 面向对象的编程(OOP)
    • 面向对象的测试(OOT)
    • 面向对象的软件维护(OOSM)

总结: 
面向过程程序设计:数据结构 + 算法 
主要解决科学计算问题,用户需求简单而固定


特点: 
分析解决问题所需要的步骤 
利用函数实现各个步骤 
依次调用函数解决问题


问题: 
软件可重用性差 
软件可维护性差 
构建的软件无法满足用户需求


面向对象程序设计:由现实世界建立软件模型 
将现实世界中的事物直接映射到程序中,可直接满足用户需求


特点: 
直接分析用户需求中涉及的各个实体 
在代码中描述现实世界中的实体 
在代码中关联各个实体协同工作解决问题


优势: 
构建的软件能够适应用户需求的不断变化 
直接利用面向过程方法的优势而避开其劣势

4.对C语言的加强

4.1命名空间

1.基本常识

所谓namespace,是指标识符的各种可见范围。C++标准程序库中的所有标识符都被定义于一个名为std的namespace中。

  • iostream和iostream.h格式不一样,前者没有后缀,实际上,在你的编译器include文件夹里面可以看到,二者是两个文件,打开文件就会发现,里面的代码是不一样的。后缀为.h的头文件c++标准已经明确提出不支持了,早些的实现将标准库功能定义在全局空间里,声明在带.h后缀的头文件里,c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。 因此:

    • 当使用<iostream.h>时,相当于在c中调用库函数,使用的是全局命名空间,也就是早期的c++实现;

    • 当使用<iostream>的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。

  • 由于namespace的概念,使用C++标准程序库的任何标识符时,可以有三种选择:

    • 直接指定标识符。例如std::ostream而不是ostream。完整语句如下: std::cout << std::hex << 3.4 << std::endl;
    • 使用using关键字。 using std::cout; using std::endl; using std::cin; 以上程序可以写成 cout << std::hex << 3.4 << endl;
    • 最方便的就是使用using namespace std; 例如: using namespace std;这样命名空间std内定义的所有标识符都有效(曝光)。就好像它们被声明为全局变量一样。那么以上语句可以如下写: cout <<hex << 3.4 << endl;
  • 因为标准库非常的庞大,所以程序员在选择的类的名称或函数名 时就很有可能和标准库中的某个名字相同。所以为了避免这种情况所造成的名字冲突,就把标准库中的一切都被放在名字空间std中。但这又会带来了一个新问 题。无数原有的C++代码都依赖于使用了多年的伪标准库中的功能,他们都是在全局空间下的。所以就有了<iostream.h><iostream>等等这样的头文件,一个是为了兼容以前的C++代码,一个是为了支持新的标准。命名空间std封装的是标准程序库的名称,标准程序库为了和以前的头文件区别,一般不加”.h”

  • C中的命名空间

    • 在C语言中只有一个全局作用域
    • C语言中所有的全局标识符共享同一个作用域
    • 标识符之间可能发生冲突
  • C++中提出了命名空间的概念 
    • 命名空间将全局作用域分成不同的部分
    • 不同命名空间中的标识符可以同名而不会发生冲突
    • 命名空间可以相互嵌套
    • 全局作用域也叫默认命名空间

2.命名空间定义及使用语法

  • C++命名空间的定义: 
    namespace name { … }

  • C++命名空间的使用:

    • 使用整个命名空间:using namespace name;
    • 使用命名空间中的变量:using name::variable;
    • 使用默认命名空间中的变量:::variable
    • 默认情况下可以直接使用默 认命名空间中的所有标识符

#include <iostream>using namespace std;//c++的命名空间namespace nameA{    int a = 10;}namespace nameB{    int a = 200;    namespace nameC{        struct Teacher{            char name[64];            int age;          };    }}int main(){    using namespace nameA;    using nameB::nameC::Teacher;    cout<<"nameA: "<<a<<endl;    cout<<"nameB: "<<nameB::a<<endl;    Teacher t = {"aaa",32};    cout<<"t.age "<<t.age<<endl;    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31

3.结论:

  • 当使用的时候,该头文件没有定义全局命名空间,必须使用namespace std;这样才能正确使用cout。若不引入using namespace std ,需要这样做。std::cout。
  • c++标准为了和C区别开,也为了正确使用命名空间,规定头文件不使用后缀.h。
  • C++命名空间的定义: namespace name { … }
  • using namespace NameSpaceA;
  • namespce定义可嵌套。

4.2实用性增强

C语言中的变量都必须在作用域开始的位置定义!!C++中更强调语言的“实用性”,所有的变量都可以在需要使用时再定义。

int main(){    int i = 0;    cout<<"ddd"<<endl;    int k;    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

4.3register增强

register关键字 请求编译器让变量a直接放在寄存器里面,速度快.在c语言中 register修饰的变量不能取地址,但是在c++里面做了增强。

1.register关键字的变化 
register关键字请求“编译器”将局部变量存储于寄存器中

  • C语言中无法取得register变量地址
  • 在C++中依然支持register关键字
  • C++编译器有自己的优化方式,不使用register也可能做优化
  • C++中可以取得register变量的地址

2.C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效。

3.早期C语言编译器不会对代码进行优化,因此register变量是一个很好的补充。

int main(){    register int a = 0;     cout<<"&a = %x\n"<<&a<<endl;    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

4.4变量检测增强

  • 在C语言中,重复定义多个同名的全局变量是合法的
  • 在C++中,不允许定义多个同名的全局变量
  • C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上

int g_var;int g_var = 1;
  • 1
  • 2
  • 3
  • C++直接拒绝这种二义性的做法。

4.5struct增强

  • C语言的struct定义了一组变量的集合,C编译器并不认为这是一种新的类型,声明结构体变量的时候需要带上struct关键字或者事先使用typedef关键字声明。

  • C++中的struct是一个新类型的定义声明,声明结构体变量的时候可不带struct关键字。

  • struct和class具有相同的功能,但是也有区别。

struct Student{    char name[100];    int age;};int main(int argc, char *argv[]){    Student s1 = {"wang", 1};    Student s2 = {"wang2", 2};        return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

4.6类型增强

C++中所有的变量和函数都必须有类型 
C语言中的默认类型在C++中是不合法的

f(i){    printf("i = %d\n", i);}g(){    return 5;}int main(int argc, char *argv[]){    f(10);    printf("g() = %d\n", g(1, 2, 3, 4, 5));    getchar();      return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 函数f的返回值是什么类型,参数又是什么类型?
  • 函数g可以接受多少个参数? 
    上述程序再C++编译器编译不通过,因为对变量和函数类型进行了增强。

在C语言中 
int f( );表示返回值为int,接受任意参数的函数 
int f(void);表示返回值为int的无参函数 
在C++中 
int f( );和int f(void)具有相同的意义,都表示返回值为int的无参函数 
C++更加强调类型,任意的程序元素都必须显示指明类型

4.7新增bool类型

  • C++在C语言的基本类型系统之上增加了bool
  • C++中的bool可取的值只有true和false
  • 理论上bool只占用一个字节,
  • 如果多个bool变量定义在一起,可能会各占一个bit,这取决于编译器的实现

  • true代表真值,编译器内部用1来表示

  • false代表非真值,编译器内部用0来表示

  • bool类型只有true(非0)和false(0)两个值

  • C++编译器会在赋值时将非0值转换为true,0值转换为false
int main(int argc, char *argv[]){    int a;    bool b = true;    printf("b = %d, sizeof(b) = %d\n", b, sizeof(b));    b = 4;    a = b;    printf("a = %d, b = %d\n", a, b);    b = -4;    a = b;    printf("a = %d, b = %d\n", a, b);    a = 10;    b = a;    printf("a = %d, b = %d\n", a, b);    b = 0;    printf("b = %d\n", b);    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24

4.8三目运算符增强

  • C语言返回变量的值 C++语言是返回变量本身
  • C语言中的三目运算符返回的是变量值,不能作为左值使用
  • C++中的三目运算符可直接返回变量本身,因此可以出现在程序的任何地方

注意:三目运算符可能返回的值中如果有一个是常量值,则不能作为左值使用(a < b ? 1 : b )= 30;

  • C语言如何支持类似C++的特性呢? 
    当左值的条件:要有内存空间;C++编译器帮助程序员取了一个地址而已。 
    思考:如何让C中的三目运算法当左值呢? 
    答:*(a < b ? &a :& b )= 30;
#include <iostream>using namespace std;//在C语言中 表达式的结果 放在什么地方 寄存器//1 // 在C语言中, 表达式的返回值 是变量的值// 在C++中, 表达式返回的是变量的本身 //2 如何做到的//让表达式返回一个内存空间 ..内存首地址 指针//在C语言中 如何 实现 c++的效果//3 本质 //c++编译器 帮我们程序员完成了 取地址的工作 int main(){    int a = 10;    int b = 20;    int var = 100;    var = 101;    //返回一个最小数 并且给最小数赋值成3    //C语言三目运算符是一个表达式 ,表达式不可能做左值    (a < b ? a : b )= 30;    //int z = (a < b ? a : b );    printf("a = %d, b = %d\n", a, b);    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37

5.const关键字

5.1基础知识

int main(){const int a;int const b;const int *c;int * const d;const int * const e ;return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 初级理解:const是定义常量==》const意味着只读

  • 含义:

    • 第一个第二个意思一样 代表一个常整形数
    • 第三个 c是一个指向常整形数的指针(所指向的内存数据不能被修改,但是本身可以修改)
    • 第四个 d 常指针(指针变量不能被修改,但是它所指向内存空间可以被修改)
    • 第五个 e一个指向常整形的常指针(指针和它所指向的内存空间,均不能被修改)
  • Const好处

    • 合理的利用const,
    • 1指针做函数参数,可以有效的提高代码可读性,减少bug;
    • 2清楚的分清参数的输入和输出特性
  • int setTeacher_err( const Teacher *p) 
    Const修饰形参的时候,不能利用形参修改指针所向的内存空间

5.2C语言的冒牌货

int main(){    const int a = 10;    int *p = (int*)&a;     printf("a===>%d\n", a);    *p = 11;    printf("a===>%d\n", a);    printf("Hello......\n");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12

解释: 
C++编译器对const常量的处理 
当碰见常量声明时,在符号表中放入常量


问题:那有如何解释取地址 
编译过程中若发现使用常量则直接以符号表中的值替换 
编译过程中若发现对const修饰的变量使用了extern或者&操作符,则给对应的常量分配存储空间(兼容C)


注意: 
C++编译器虽然可能为const常量分配空间,但不会使用其存储空间中的值。

5.3与#define的关系

C++中的const修饰的,是一个真正的常量,而不是C中变量(只读)。const修饰的常量在编译期间,就已经确定下来了。

int main(){    const int a = 1;     const int b = 2;     int array[a + b ] = {0};    int i = 0;     for(i=0; i<(a+b); i++)    {        printf("array[%d] = %d\n", i, array[i]);    }    getchar();    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • C++中的const常量类似于宏定义 
    const int c = 5; ≈ #define c 5
  • C++中的const常量与宏定义不同 
    • const常量是由编译器处理的,提供类型检查和作用域检查
    • 宏定义由预处理器处理,单纯的文本替换
void fun1(){    #define a 10    const int b = 20;    //#undef a  # undef}void fun2(){    printf("a = %d\n", a);    //printf("b = %d\n", b);//编译不通过,因为b的作用域仅限于func1()}int main(){    fun1();    fun2();    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

5.4结论

  • C语言中的const变量 
    C语言中const变量是只读变量,有自己的存储空间

  • C++中的const常量 
    -可能分配存储空间,也可能不分配存储空间

    • 当const常量为全局,并且需要在其它文件中使用,会分配存储空间
    • 当使用&操作符,取const常量的地址时,会分配存储空间
    • 当const int &a = 10;

    • const修饰引用时,也会分配存储空间

6.新引入的“引用”概念

1.背景

  • 变量名回顾 
    变量名实质上是一段连续存储空间的别名,是一个标号(门牌号)
  • 程序中通过变量来申请并命名内存空间 
    通过变量的名字可以使用存储空间

  • 问题1:对一段连续的内存空间只能取一个别名吗?

2.引用概念 
a) 在C++中新增加了引用的概念 
b) 引用可以看作一个已定义变量的别名 
c) 引用的语法:Type& name = var;必须有初始值; 
d) 引用做函数参数?(引用作为函数参数声明时不进行初始化,即形参不需初始化)

void main01(){    int a = 10; //c编译器分配4个字节内存。。。a内存空间的别名    int &b = a;  //b就是a的别名。。。    a =11; //直接赋值    {        int *p = &a;        *p = 12;        printf("a %d \n",a);    }    b  = 14;     printf("a:%d b:%d", a, b);    system("pause");}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15

3.作函数参数 
普通引用在声明时必须用其它的变量进行初始化, 
引用作为函数参数声明时不进行初始化

//复杂数据类型 的引用struct Teacher{    char name[64];    int age ;};void printfT(Teacher *pT){    cout<<pT->age<<endl;}//pT是t1的别名 ,相当于修改了t1void printfT2(Teacher &pT){    //cout<<pT.age<<endl;    pT.age = 33;}//pT和t1的是两个不同的变量void printfT3(Teacher pT){    cout<<pT.age<<endl;    pT.age = 45; //只会修改pT变量 ,不会修改t1变量}void main(){    Teacher t1;    t1.age = 35;    printfT(&t1);    printfT2(t1); //pT是t1的别名    printf("t1.age:%d \n", t1.age); //33    printfT3(t1) ;// pT是形参 ,t1 copy一份数据 给pT     //---> pT = t1    printf("t1.age:%d \n", t1.age); //35    cout<<"hello..."<<endl;        system("pause");    return ;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

4.意义 
1)引用作为其它变量的别名而存在,因此在一些场合可以代替指针 
2)引用相对于指针来说具有更好的可读性和实用性

5.思考

  • 思考1:C++编译器背后做了什么工作?
int main(){    int a = 10;    int &b = a;    //b是a的别名,请问c++编译器后面做了什么工作?    b = 11;    cout<<"b--->"<<a<<endl;    printf("a:%d\n", a);    printf("b:%d\n", b);    printf("&a:%d\n", &a);    printf("&b:%d\n", &b);  //请思考:对同一内存空间可以取好几个名字吗?    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14

单独定义的引用时,必须初始化;说明很像一个常量

  • 思考2:普通引用有自己的空间吗?
struct Teacer {    int &a;    int &b;};int main(){    printf("sizeof(Teacher) %d\n", sizeof(Teacer));    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10

64位系统

说明引用占的内存空间大小和指针一样

6.本质

1)引用在C++中的内部实现是一个常指针 
Type& name == Type* const name 
2)C++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同。 
3)从使用的角度,引用会让人误会其只是一个别名,没有自己的存储空间。这是C++为了实用性而做出的细节隐藏

4)间接赋值成立的三个条件

  • 定义两个变量 (一个实参一个形参)
  • 建立关联 实参取地址传给形参
  • *p形参去间接的修改实参的值

结论:1)引用在实现上,只不过是把:间接赋值成立的三个条件的后两步和二为一 
//当实参传给形参引用的时候,只不过是c++编译器帮我们程序员手工取了一个实参地址,传给了形参引用(指针常量) 
2)当我们使用引用语法的时,我们不去关心编译器的引用是怎么做的; 
当我们分析奇怪的语法现象的时,我们才去考虑c++编译器是怎么做的。

7.函数的返回值是引用

  • 综述 
    C++引用使用时的难点:

    • 当函数返回值为引用时 
      • 若返回栈变量
      • 不能成为其它引用的初始值
      • 不能作为左值使用
    • 若返回静态变量或全局变量 
      • 可以成为其他引用的初始值
      • 即可作为右值使用,也可作为左值使用
    • C++链式编程中,经常用到引用,运算符重载专题
  • 返回值是基础类型

#include <iostream>using namespace std;int getAA1(){    int a ;    a = 10;    return a;}//返回a的本身 返回a的一个副本 10int& getAA2(){    int a ; //如果返回栈上的 引用, 有可能会有问题    a = 10;    return a;}int* getAA3(){    int a ;    a = 10;    return &a;}int main(){    int a1 = 0;    int a2 = 0;    a1 = getAA1();    a2 = getAA2(); //10    int &a3 = getAA2();  //若返回栈变量   不能成为其它引用的初始值,否则会是未知值    printf("a1:%d \n", a1);    printf("a2:%d \n", a2);    printf("a3:%d \n", a3);  // *a3    cout<<"hello..."<<endl;    system("pause");    return 1;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

  • 返回值是static变量
#include <iostream>using namespace std;//static修饰变量的时候,变量是一个状态变量int j(){    static int a = 10;    a ++;    printf("a:%d \n", a);    return a;}int& j1(){    static int a = 10;    a ++;    printf("a:%d \n", a);    return a;}int *j2(){    static int a = 10;    a ++;    printf("a:%d \n", a);    return &a;}int main(){    // j()的运算结果是一个数值,没有内存地址,不能当左值。。。。。    //11 = 100;    //*(a>b?&a:&b) = 111;    //当被调用的函数当左值的时候,必须返回一个引用。。。。。    j1() = 100; //编译器帮我们打造了环境    j1();    *(j2()) = 200; //相当于我们程序员手工的打造 做左值的条件    j2();    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45

  • 返回值是形参
#include <iostream>using namespace std;int  g1(int *p){    *p = 100;    return *p;}int&  g2(int *p) //{    *p = 100;    return *p;}//当我们使用引用语法的时候 ,我们不去关心编译器引用是怎么做的//当我们分析乱码这种现象的时候,我们才去考虑c++编译器是怎么做的。。。。int main(){    int a1 = 10;    a1 = g2(&a1);    int &a2 = g2(&a1); //用引用去接受函数的返回值,是不是乱码,关键是看返回的内存空间是不是被编译器回收了。。。。    printf("a1:%d \n", a1);    printf("a2:%d \n", a2);    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

  • 返回值非基础类型 
    如果返回引用不是基础类型,是一个类,那么情况非常复杂。。涉及到copy构造函数和=操作重载,抛砖。。。。

8.指针引用

就是数据类型是指针类型的引用,示例代码如下:

#include <iostream>using namespace std;/*定义一个类*/class Teacher{public:    char name[64];    int age;};/*使用传统C语言的二级指针模型*/int getTeacher(Teacher ** src_ptr){    Teacher * tmp = NULL;    if (NULL == src_ptr)    {        return -1;    }    tmp = (Teacher*)malloc(sizeof(Teacher));    if (NULL == tmp)    {        return -2;    }    tmp->age = 36;    *src_ptr = tmp;    return 0;}/*使用面向对象的C++的引用*/int getTeacher01(Teacher * &src_ptr){    src_ptr = (Teacher*)malloc(sizeof(Teacher));//引用本质就是指针常量(该指针不能被修改,但指向的内容可修改)    if (NULL == src_ptr)    {        return  -1;    }    src_ptr->age = 32;    return 0;}/*内存回收,避免内存泄露*/void freeTeacher(Teacher *src_ptr)//可优化成二级指针做参数更完美{    if (src_ptr == NULL)    {        return;    }    free(src_ptr);}int main(void){    Teacher *t1= NULL;    /*使用二级指针模型*/    cout << "use second rank pointer" << endl;    getTeacher(&t1);    cout << t1->age << endl;    freeTeacher(t1);    /*直接使用引用*/    cout << "use quote" << endl;    getTeacher01(t1);    cout << t1->age << endl;    freeTeacher(t1);    /*避免野指针*/    t1 = NULL;    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73
  • 74
  • 75
  • 76
  • 77
  • 78

9.常引用

  • 如果用变量初始化常引用,使得引用变量(引用代表的内存空间)具有只读属性,只能通过指针的方式修改变量或者通过修改原变量的方式修改

  • 如果使用字面值初始化常引用,使得编译器为字面值分配内存空间而不是放到符号表,引用名就是该段空间的别名

#include <iostream>using namespace std;int main(void){    /*普通引用*/    int x = 10;    int & y = x;    cout << y << endl;    /*用变量初始化常引用*/    int a = 20;    const int & b = a;    cout << b << endl;    /*通过修改原普通变量的方式修改内存里的值*/    a = 100;    cout << a << endl;    cout << b << endl;    /*通过指针方式修改*/    int * ptr = &a;    *ptr = 200;    cout << a << endl;    cout << b << endl;    /*因为&b的类型实际是const int *类型,所以不能将他赋值给int *,因为它具有只读属性*/    /*ptr = &b;    *ptr = 200;    cout << a << endl;    cout << b << endl;*/    /*b的类型是const int类型,是一个常量,所以不能给常量赋值*/    //b = 500;    /*再次说明引用的本质是一段内存块的别名,因为他们表示的内存块的首地址相同,说明是同一块内存*/    cout << &a << endl;    cout << &b << endl;    /*用字面值(字面常量)初始化常量引用*/    const int var = 30;//var放在符号表中    //int & var00 = 35;//编译器不会给字面值35分配内存空间,与“引用是一段内存空间的别名不相符”    //cout << &35 << endl;//正因为编译器不给分配空间,所以不能对字面值35进行取地址操作    const int & var01 = 40;//编译器会为40分配内存空间,var01就是这段空间的别名    cout << var01 << endl;    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52

总结: 
1)const int & e 相当于 const int * const e 
2)普通引用 相当于 int *const e 
3)当使用常量(字面量)对const引用进行初始化时,C++编译器会为常量值分配空间,并将引用名作为这段空间的别名 
4)使用字面量对const引用初始化后,将生成一个只读变量(引用变量是只读的)

7.对C函数的扩展

7.1 关于内联函数的几点说明:

  1. 必须inline int myfunc(int a, int b)和函数体的实现,写在一块
  2. C++编译器直接将函数体插入在函数调用的地方,内联函数在最终生成的代码中是没有定义的
  3. 内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
  4. C++编译器不一定准许函数的内联请求!
  5. 内联函数是一种特殊的函数,具有普通函数的特征(参数检查,返回类型等)
  6. 内联函数由编译器处理,直接将编译后的函数体插入调用的地方,宏代码片段由预处理器处理,进行简单的文本替换,没有任何编译过程
  7. 现代C++编译器能够进行编译优化,因此一些函数即使没有inline声明,也可能被编译器内联编译 
    另外,一些现代C++编译器提供了扩展语法,能够对函数进行强制内联 
    如:g++中的__attribute__((always_inline))属性
  8. C++中内联编译的限制: 

    • 不能存在任何形式的循环语句
    • 不能存在过多的条件判断语句
    • 函数体不能过于庞大
    • 不能对函数进行取址操作
    • 函数内联声明必须在调用语句之前
  9. 编译器对于内联函数的限制并不是绝对的,内联函数相对于普通函数的优势只是省去了函数调用时压栈,跳转和返回的开销。因此,当函数体的执行开销远大于压栈,跳转和返回所用的开销时,那么内联将无意义。

  10. 和宏代码片段想比还是具有优势的

#include <iostream>using namespace std;/*inline关键字必须和函数实现体写在一块,否则编译器不理会内联要求*/inline void printA(){    int a = 10;    cout << a << endl;}/*传统C语言的宏代码片段,具有副作用,尤其是在前置++操作符出现的时候*/#define MYFUNC(a,b) ((a) < (b) ?(a):(b))/*内联函数形式完成同样功能,也能节约开销,直接插入代码片段,进行参数和类型检查*/inline int myfunc(int a, int b){    return a < b ? a : b;}int main(void){    printA();    /*相当于将printA的函数体照搬到这里展开*/    /*    {        int a = 10;        cout << a << endl;    }       */    int b = 20;    int c = 30;    /*宏代码片段方式*/    int d = MYFUNC(++b,c);    cout << b << endl << c << endl << d << endl;    /*内联函数方式*/    b = 20;    c = 30;    d = myfunc(++b, c);    cout << b << endl << c << endl << d << endl;    system("pause");    return 0;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

7.2 函数参数

1.默认参数

  • C++中可以在函数声明时为参数提供一个默认值, 
    当函数调用时没有指定这个参数的值,编译器会自动用默认值代替

  • 函数默认参数的规则 
    只有参数列表后面部分的参数才可以提供默认参数值,一旦在一个函数调用中开始使用默认参数值,那么这个参数后的所有参数都必须使用默认参数值

void myPrint(int x = 3)//默认参数是3{    cout<<"x"<<x<<endl;}//1 若 你填写参数,使用你填写的,不填写使用默认值//2 在默认参数规则 ,如果默认参数出现,那么右边的都必须有默认参数void myPrint2( int m, int n, int x = 3, int y = 4)//void myPrint2( int m, int n, int x = 3, int y )//错误函数签名{    cout<<"x"<<x<<endl;}void main_默认参数(){    myPrint(4);    myPrint();     cout<<"hello..."<<endl;    system("pause");    return ;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23

2.占位参数

函数占位参数

占位参数只有参数类型声明,而没有参数名声明 
一般情况下,在函数体内部无法使用占位参数

/函数占位参数 函数调用时,必须写够参数void func1(int a, int b, int){    cout<<"a"<<a<<" b"<<b<<endl;}void main_占位参数(){    //func1(1, 2); //err调用不起来    func1(1, 2, 3);    cout<<"hello..."<<endl;    system("pause");    return ;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17

3.两者关系 
可以将占位参数与默认参数结合起来使用 
意义 
为以后程序的扩展留下线索 
兼容C语言程序中可能出现的不规范写法

//默认参数和占位参数void  func2(int a, int b, int =0){    cout<<"a="<<a<<";b="<<b<<endl;}void main(){    func2(1, 2); //0k    func2(1, 2, 3); //ok    cout<<"hello..."<<endl;    system("pause");    return ;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16

结论:如果默认参数和占位参数在一起,都能调用起来

7.3 函数重载

1.基本概念

  • 函数重载概念 
    函数重载(Function Overload) 
    用同一个函数名定义不同的函数 
    当函数名和不同的参数搭配时函数的含义不同

  • 函数重载的判断标准 
    函数重载至少满足下面的一个条件: 
    参数个数不同 
    参数类型不同 
    参数顺序不同

  • 函数返回值不是函数重载的判断标准


2.函数重载的调用准则

  • 将所有同名函数作为候选者
  • 尝试寻找可行的候选函数 
    • 精确匹配实参
    • 通过默认参数能够匹配实参
    • 通过默认类型转换匹配实参
  • 匹配失败 
    • 最终寻找到的可行候选函数不唯一,则出现二义性,编译失败。
    • 无法匹配所有候选者,函数未定义,编译失败。

3.函数重载的注意事项

  • 重载函数在本质上是相互独立的不同函数(静态链编)
  • 重载函数的函数类型是不同的
  • 函数返回值不能作为函数重载的依据
  • 函数重载是由函数名和参数列表决定的。
#include <iostream>using namespace std;void myPrint(int a){    printf("a:%d \n", a);}void myPrint(char *p){    printf("%s \n", p);}void myPrint(int a, int b){    printf("a:%d ", a);    printf("b:%d \n", b);}/*//返回值 不是 判断函数重载的标准 int myPrint(int a, int b){    printf("a:%d ", a);    printf("b:%d \n", b);}*///1 当函数名和不同的参数搭配时函数的含义不同//2 函数重载的判断标准//名称 参数 返回值//名称相同 参数不一样(个数/类型/)//3 返回值 不是 判断函数重载的标准 /////4 重载函数的调用标准        //void main1601(){    myPrint(1);    myPrint("111222233aaaa");    myPrint(1, 2);    cout<<"hello..."<<endl;    system("pause");    return ;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48

4.函数重载和函数默认参数混搭

容易出现二义性导致编译失败

// 函数重载  和  函数默认参数 在一起void myfunc(int a, int b, int c = 0){    printf("a:%d b:%d c:%d \n", a, b, c);}void myfunc(int a, int b){    printf("a:%d b:%d\n", a, b);}void myfunc(int a){    printf("a:%d\n", a);}void main1602(){    //myfunc(1, 2); //函数调用时,会产生二义性    myfunc(1);    cout<<"hello..."<<endl;    system("pause");    return ;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26

5.函数重载和函数指针

重载函数和函数指针所表示的函数类型要匹配才能将重载函数给函数指针赋值。

#include <iostream>using namespace std;void myfunc(int a){    printf("a:%d \n", a);}void myfunc(char *p){    printf("%s \n", p);}void myfunc(int a, int b){    printf("a:%d \n", a);}void myfunc(char *p1, char *p2){    printf("p1:%s ", p1);    printf("p2:%s \n", p2);}//函数指针 基础的语法//1声明一个函数类型typedef void (myTypeFunc)(int a, int b);  //int//myTypeFunc *myfuncp = NULL; //定义一个函数指针 这个指针指向函数的入口地址//声明一个函数指针类型 typedef void(*myPTypeFunc)(int a, int b);  //声明了一个指针的数据类型 //myPTypeFunc fp = NULL;  //通过  函数指针类型 定义了 一个函数指针 ,//定义一个函数指针 变量void(*myVarPFunc)(int a, int b);//void main(){    myPTypeFunc fp; //定义了一个 函数指针 变量,指向的函数类型是void (int a,int b),                    //在进行重载函数给它赋值的时候就会确定使用哪一个函数,因为他们类型要一致    fp = myfunc;    //fp(1);//错误的调用,由fp的类型决定    //myVarPFunc = myfunc;    fp(1, 2);    /*    {    char buf1[] = "aaaaafff";    char buf2[] = "bbbb";    fp(buf1, buf2);//错误的调用,由fp的类型决定    }    */    cout << "hello..." << endl;    system("pause");    return;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62

附录1:C++语言对C语言扩充和增强的几点具体体现

 
 
 
 

附录2:C语言register关键字—最快的关键字

 
 

原创粉丝点击