Jonathan's Studio

回顾C、C++、Java

字数统计: 10.8k阅读时长: 40 min
2020/04/10

回顾C、C++、Java

在阿里的第一面的时候,出现了基础知识仍然不清不楚的现象。就此在这里回顾一些知识,以及他们和Apple的开发的关系。
本文大量收集了Wiki内容以及相关博客的内容,并非原创,只做收集整理个人学习。

C语言

C语言是一门面向过程的、抽象化的通用程序设计语言,广泛应用于底层开发。C语言能以简易的方式 编译 、处理低级 存储器。C语言仅产生少量的 机器码 以及不需要任何运行环境支持便能运行的编程语言。
C语言的运用非常之多,它兼顾了 高级语言 和汇编语言的优点,相较于其它编程语言具有较大优势。计算机系统设计以及应用程序编写是C语言应用的两大领域。同时,C语言的普适较强,在许多计算机操作系统中都能够得到适用,且效率显著。

C语言具有的特性

  • C语言是一个有结构化程序设计、具有变量作用域(variable scope)以及递归功能的过程式语言。
  • C语言传递参数均是以值传递(pass by value),另外也可以传递指针(a pointer passed by value)。
  • 不同的变量类型可以用结构体(struct)组合在一起。结构数据类型允许构造由多个基础数据类型组合而成的复杂结构,结构数据类型为面向对象的蓝本。
  • 只有32个保留字(reserved keywords),使变量、函数命名有更多弹性。
  • 部分的变量类型可以转换,例如整型和字符型变量。
  • 透过 指针(pointer),C语言可以容易的对存储器进行低端控制。
  • 编译预处理(preprocessor)让C语言的编译更具有弹性。
    编译预处理阶段,读取C源程序,对其中的预处理指令(以#开头的指令)和特殊符号进行处理。或者说是扫描源代码,对其进行初步的转换,产生新的源代码提供给编译器。
    预处理过程先于编译器对源代码进行处理,读入源代码,检查包含预处理指令的语句和宏定义,并对源代码进行转换。预处理过程还会删除程序中的注释和多余的空白字符。
    1
    2
    3
    4
    预处理指令主要有以下三种:
    1)包含文件:将源文件中以#include格式包含的文件复制到编译的源文件中,可以是头文件,也可以是其它的程序文件。
    2)宏定义指令:#define 指令定义一个宏,#undef指令删除一个宏定义。
    3)条件编译:根据#ifdef和#ifndef后面的条件决定需要编译的代码。

C语言的优点

  1. 代码量小
    C语言的代码量很小,这是什么意思呢?也就是说如果你要完成同样一个 功能,用C语言编写出来的程序的容量是很小的,而用其他语言编写容量就会比较大。
  2. 运行速度快
    世界上总共有三大操作系统:UNIX 操作系统是用纯C语言编写的;Windows 操作系统的内核也是用C语言编写的;Linux 操作系统仍是用纯C语言编写的。
  3. 功能强大
    C语言中提供了大量的函数其中包括系统生成的函数和用户定义的函数。 C编译器自带的头文件,其中包括可用于开发程序的许多基本功能列表。同时,程序员还可以创建功能,按他们的要求被称为用户生成/定义的函数。同时,C语言可以访问硬件,是嵌入式开发的重要语言。

C语言的缺点

  1. 缺少面向对象编程功能
    面向对象编程的特点是C语言缺少的,你只能使用面向过程的语言来开发程序。这就是C++等语言诞生的缘由。
  2. 运行时类型检查是不可用
    在C语言没有规定运行时类型检查,比如我传递浮点值,而接收参数为整数类型那么值将会被改变,它不会给任何类型的错误信息。
    一些检查他需要程序员自行设计,造成麻烦。
  3. 构造函数和析构函数不可用:
    C不提供面向对象的特性,因此它不具有构造和析构功能。构造函数和析构函数用于构造对象和销毁对象。因此,在C语言中,你必须通过方法或者以其他方式来手动实现变量的析构和构造。

C++语言

C++是一种被广泛使用的计算机程序设计语言。它是一种通用 程序设计语言 ,支持 多重编程模式 ,例如 过程化程序设计数据抽象面向对象程序设计泛型程序设计设计模式 等。
起初,这种语言被称作“C with Classes”(“包含‘’的C语言”),作为 C语言 的增强版出现。随后,C++不断增加新特性。 虚函数 (virtual function)、 运算符重载 (operator overloading)、 多继承 (multiple inheritance)、 标准模板库 (standard template library, STL)、 异常处理 (exception)、 运行时类型 信息(Runtime type information)、 名字空间 (namespace)等概念逐渐纳入标准。

C++语言的设计原则

  • C++设计成直接的和广泛的支持多种程序设计风格( 过程化程序设计数据抽象面向对象编程泛型程序设计 )。
  • C++设计成给程序设计者更多的选择,即使可能导致程序设计者选择错误。
  • C++设计成尽可能与C兼容,借此提供一个从C到C++的平滑过渡。
  • C++避免平台限定或没有普遍用途的特性。
  • C++不使用会带来额外开销的特性。
  • C++设计成无需复杂的程序设计环境。

C++语言中的特色

和C语言相比,C++引入了更多的特性,包括:复合类型(引用类型等)、const限定符和constexpr常量表达式、类型处理运算符(类型别名及auto和decltype等多种类型指示符)、C++标准库(IO库与多种容器类)与迭代器、动态内存与智能指针、函数重载、面向对象程序设计(如数据抽象、成员函数、类作用域、构造函数与析构函数、静态成员、访问控制与继承、虚函数、抽象类与接口等)、拷贝控制、运算符重载、造型与函数风格的强制类型转换、模板与泛型编程,以及异常处理、名字空间、多继承与虚继承、运行时类型识别及嵌套类等。

C++与C之间的不兼容

C++有时被认为是C的 超集 (superset),但这并不严谨。

  • C允许从void*隐式转换到其它的指标类型,但C++不允许。
    1
    2
    int *i = malloc(**sizeof**(int) * 5);  //C  
    int *I = (int *)malloc(**sizeof**(int) * 5); //C++
  • 另一个常见的可移植问题是,C++定义了很多的新关键字,如new和class,它们在C程序中,是可以作为识别字(例:变量名)的。
  • 由于C++函数和C函数通常具有不同的 名字修饰调用约定 ,所有在C++中调用的C函数,须放在extern “C” { /* C函数声明 */ }之内。

C++语言的几大特性

模板

模板(Template)指C++编程语言中的函数模板(function template)与类模板(class template),这种观念是取材自 Simula 的泛型程序设计。它采用 typenameclass 两个关键字,来标识模板类别的类型参数。 C++11C++14 分别引入了类型别名模板和变量模板。

1
2
3
4
5
6
//函数模板
 template <class 形参名,class 形参名,......>
 返回类型 函数名(参数列表) {...}
//类模板
 template<class 形参名,class 形参名,…>
 class 类名{ ... };

类别与对象

在面向对象对象程序设计术语中,对象(object)是数据(data)和处理数据的指令(instructions)的联合(association)。模拟(simulate)实际世界(real-world),对象有三种特质(characteristics):状态(State)、行为(Behavior)、 同一性身份 ,并且使用消息(message)来引发彼此的交互。类别(class)为对象的 蓝图 或工厂,定义了对象的抽象特质,包括对象的属性特质和对象的行为特质,属性的值即是对象的状态,行为即是对象能够做的事。
C++为类别构成式面向对象编程语言(class-based object-oriented programming language),类别概念具现化(reification)地作为二等公民(second-class citizen)出现在C++语言当中,在语法中明确地使用类别来做到数据抽象、封装、模块化、继承、子类型多态、对象状态的自动初始化。C++中,一个类别即为一个类型,加上封装,一个类别即为一个 抽象数据类型 (Abstract Data Type,ADT),继承、多态、模板都加强了类别的可抽象性。在C++可以使用class或struct这两个关键字宣告类别(class),而使用new运算符实体化类别产生的实体(instance)即为对象,是 一等公民 。C/C++以数据成员(data member)表达属性,以成员函数(member function)表达行为。

1
2
3
4
5
6
7
声明一个Car class:
class Car {
private:
int isRunning;
public:
Run();
};

但是仍然需要注意,严格来说,C++中对象的概念和C的对应概念接近,表示的是具有特定类型的存储,而非面向对象意义上的“对象”:一个对象不一定是类类型的。此外,C++意义上的“实例”仅指模板实例化的结果,而并不指对象。作为对比, Java 的“对象”和“实例”的概念和这里的使用一致。
一个常见的混淆其实只是一个微妙的术语问题:由于它的演化来自C,在C++中的术语 对象 和C语言一样是意味着存储器区域,而不是类的实体,在其它绝大多数的 面向对象 语言也是如此。举例来说,在C和C++中,语句int i;定义一个int类型的对象,这就是变量的值i将在指派时,所存入的存储器区域。

封装

封装(Encapsulation)是将数据和处理数据的 程序 (procedure)组合起来,仅对外公开接口(interface),达到信息隐藏(information hiding)的功能。封装的优点是能减少耦合(Coupling)。C++、Java、C# 等语言定义对象都是在语法中明确地使用类别(Class)来做到封装。
C++的类对其成员(包括数据成员、函数成员)分为三种封装状态:
* 公有(public):类别的用户可以访问、使用该类别的此种成员。
* 保护(protected):该类别的派生类可以访问、使用该类别的此成员。外部程序代码不可以访问、使用这种成员。
* 私有(private):只有类别自身的成员函数可以访问、使用该类别的此成员。
一般可以将C++类的对外接口设定为公有成员;类内部使用的数据、函数设定为私有成员;供派生自该类别的子类使用的数据、函数设定为保护成员。

继承

继承(Inheritance)是指派生类(subclass)继承基类(superclass),会自动获取超类别除私有特质外的全部特质,同一类别的所有实体都会自动有该类别的全部特质,做到代码再用(reuse)。C++只支持类别构成式继承,虽然同一类别的所有实体都有该类别的全部特质,但是实体能够共享的实体成员只限成员函数,类别的任何实体数据成员乃每个实体独立一份,因此对象间并不能共享状态,除非特质为参考类型的属性,或使用指针来间接共享。C++支持的继承关系为:

  • 公有继承(public inheritance):最常用继承关系,含义是“is-a”关系,代表了在完全使用公有继承的对象类别之间的层次关系(hierarchy)。
    当一个类派生自公有基类时,基类的公有成员也是派生类的公有成员,基类的保护成员也是派生类的保护成员,基类的私有成员不能直接被派生类访问,但是可以通过调用基类的公有保护成员来访问。
  • 受保护继承(protected inheritance):基类的公有或保护内容可以被派生类,以及由此派生的其他类别使用。但是基类对外界用户是不可见的。派生类的用户不能访问基类的成员、不能把派生类别转换(造型)为基类的指针或引用。
    当一个类派生自保护基类时,基类的公有保护成员将成为派生类的保护成员。
  • 私有继承(private inheritance):基类的公有或保护内容仅可以被派生类访问。但基类对派生类的子类或派生类的用户都是不可见的。派生类的子类或派生类的用户都不能访问基类的内容、不能把派生类转换为基类的指针或引用。
    当一个类派生自私有基类时,基类的公有保护成员将成为派生类的私有成员。

访问控制和继承在这里做出这样的总结
访问 | public | protected | private
-|-|-|-
同一个类 | √ | √ | √ |
派生类 | √ | √ | x |
外部的类 | √ | x | x |

C++支持多继承(multiple inheritance,MI)

多继承(multiple /inheritance,MI)的优缺点一直广为用户所争议,许多语言(如 Java )并不支持多继承,而改以单一继承和接口继承(interface inheritance),而另一些语言则采用用单一继承和混入(mixin)。C++通过 虚继承 (Virtual Inheritance)来解决多继承带来的一系列问题。

  • 虚继承是面向对象编程中的一种技术,是指一个指定的基类,在 继承体系结构中,将其成员数据实例共享给也从这个基类型直接或间接派生的其它类。
    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
    class Animal {
    public:
    virtual void eat();
    };

    // Two classes virtually inheriting Animal:
    class Mammal : public virtual Animal {
    public:
    virtual void breathe();
    };

    class WingedAnimal : public virtual Animal {
    public:
    virtual void flap();
    };

    // A bat is still a winged mammal
    class Bat : public Mammal, public WingedAnimal {
    };
    //如果在Animal类在派生中不使用virtual修饰的话 实例化Bat并调用eat()方法时会出现歧义,编译器并不知道你的eat()方法是来自Mammal还是WingedAnimal的基类的实例(继承结构的初始化实例从基类开始调用构造函数,析构从子类开始调用析构函数),因此使用virtual创造共享实例来使得animal类的实例在Bat的实例中唯一。

    //一个类如果不希望被继承,类似于Java中的具有finally性质的类,这在C++中可以用虚继承来实现(或者基类构造方法private来实现finally效果,产生编译错误):
    class Final {
    protected:
    Final() {}
    };

    class Parent: private virtual Final {
    public:
    Parent() {}
    private:
    int a, b, c;
    };

    class Child : public Parent {
    public:
    Child() {}
    };

    Parent再虚继承一个父类Final,Final的构造函数放在protected区中,而 Parent继承时使用private(和 virtual)描述符,这样会达到下面的效果
    1. Final的构造函数在protected中,且Parent用private继承 —— 因此其构造函数只能在Parent调用
    2. Parent虚继承了Final —— 因此Parent的子类Child不会通过Parent来调用 Final的构造函数,而是自己调用Final的构造函数
    以上两点加起来,就使得Final的子类 “进退两难”,引发编译错误

多态

除了封装与继承外,C++还提供了多态功能,面向对象的精神在于多态(Polymorphism),一般的多态,是指动态多态,系使用继承和动态绑定(Dynamic Binding)实现,使用多态可创建起继承体系(Inheritance hierarchy)。类(class)与继承只是达成多态中的一种手段,所以称面向对象而非面向类。
多态又分成静态多态(Static Polymorphism)与动态多态(Dynamic Polymorphism)。C++语言支持的动态多态必须结合继承和动态绑定(Dynamic Binding)方式实现。静态多态是指编译时决定的多态,包括重载和以模板(template)实现多态的方法即参数化类型(Parameterized Types),是使用宏(macro)的“程序代码膨胀法”达到多态效果。
类型转换(type cast)也是一种非参数化(ad hoc)多态的概念,C++提供dynamic_cast, static_cast等运算符来实现强制类型转换(Coercion)。
操作数重载(operator overloading)或函数重载(function overloading)也算是多态的概念。

静态多态中的函数重载
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
int Add(int left, int right)
{
return left + right;
}
double Add(double left, int right)
{
return left + right;
}

int main()
{
Add(10, 20);
//Add(10.0, 20.0); //这是一个问题代码
Add(10.0,20); //正常代码
return 0;
}

可以看出来, 静态多态是编译器在编译期间完成的,编译器会根据实参类型来选择调用合适的函数,如果有合适的函数可以调用就调,没有的话就会发出警告或者报错

动态多态中的虚函数

虚函数概念的引入可以解决这样的问题:
在面向对象程序设计中,派生类继承自基类。使用指针或引用访问派生类对象时,指针或引用本身所指向的类型可以是基类而不是派生类。如果派生类覆盖了基类中的方法,通过上述指针或引用调用该方法时,可以有两种结果:

  1. 调用到基类的方法:编译器根据指针或引用的类型决定,称作“早绑定”;
  2. 调用到派生类的方法:语言的运行时系统根据对象的实际类型决定,称作“迟绑定”。
    虚函数的效果属于后者。如果问题中基类的函数是“虚”的, 则调用到的都是最终派生类(英语:most-derived class)中的函数实现 ,与指针或引用的类型无关。反之,如果函数非“虚”,调用到的函数就在编译期根据指针或者引用所指向的类型决定。

    有了虚函数,程序甚至能够调用编译期还不存在的函数。

    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
    # include <iostream>
    # include <vector>

    using namespace std;
    class Animal
    {
    public:
    virtual void eat() const { cout << "I eat like a generic Animal." << endl; }
    virtual ~Animal() {}
    };

    class Wolf : public Animal
    {
    public:
    void eat() const { cout << "I eat like a wolf!" << endl; }
    };

    class Fish : public Animal
    {
    public:
    void eat() const { cout << "I eat like a fish!" << endl; }
    };

    class GoldFish : public Fish
    {
    public:
    void eat() const { cout << "I eat like a goldfish!" << endl; }
    };


    class OtherAnimal : public Animal
    {
    };

    int main()
    {
    std::vector<Animal*> animals;
    animals.push_back( new Animal() );
    animals.push_back( new Wolf() );
    animals.push_back( new Fish() );
    animals.push_back( new GoldFish() );
    animals.push_back( new OtherAnimal() );

    for( std::vector<Animal*>::const_iterator it = animals.begin();
    it != animals.end(); ++it)
    {
    (*it)->eat();
    delete *it;
    }

    return 0;
    }

    以下是虚函数 Animal::eat() 的输出:
    1
    2
    3
    4
    5
    I eat like a generic Animal.
    I eat like a wolf!
    I eat like a fish!
    I eat like a goldfish!
    I eat like a generic Animal.
    当 Animal::eat() 不是被宣告为虚函数时,输出如下所示:
    1
    2
    3
    4
    5
    I eat like a generic Animal.
    I eat like a generic Animal.
    I eat like a generic Animal.
    I eat like a generic Animal.
    I eat like a generic Animal.

Java语言

Java是一种广泛使用的计算机 编程语言 ,拥有 跨平台面向对象泛型编程 的特性,广泛应用于企业级Web应用开发和移动应用开发。
Java编程语言的风格十分接近 C++ 语言。继承了 C++ 语言面向对象技术的核心, 舍弃了容易引起错误的 指针 ,以 引用 取代;移除了C++中的 运算符重载 和 多重继承特性,用 接口 取代 ;增加 垃圾回收器 功能。在Java SE 1.5版本中引入了 泛型编程类型安全 的枚举、不定长参数和自动装/拆箱特性。太阳微系统对Java语言的解释是:“Java编程语言是个简单、面向对象、分布式、解释性、健壮、安全与系统无关、可移植、高性能、多线程和动态的语言”
Java不同于一般的 编译语言解释型语言 。它首先将源代码编译成 字节码 ,再依赖各种不同平台上的虚拟机来解释执行字节码,从而具有“ 一次编写,到处运行 ”的跨平台特性。在早期JVM中,这在一定程度上降低了Java程序的运行效率。但在J2SE1.4.2发布后,Java的运行速度有了大幅提升。

Java主要用途

  1. 桌面GUI应用程序 : Java通过抽象窗口工具包(AWT),Swing和JavaFX等多种方式提供GUI开发。虽然AWT包含许多预先构建的组件,如菜单,按钮,列表以及众多第三方组件,但Swing(一个GUI小部件工具包)还提供某些高级组件,如树,表格,滚动窗格,选项卡式面板和列表。JavaFX是一组图形和媒体包,提供了Swing互操作性,3D图形功能和自包含的部署模型,可以快速编写Java小应用程序和应用程序的脚本。
  2. 移动应用程序 : Java Platform,Micro Edition(Java ME或J2ME)是一个跨平台框架,用于构建可在所有Java支持的设备(包括功能手机和智能手机)上运行的应用程序。此外,最受欢迎的移动操作系统之一的Android应用程序通常使用Android软件开发工具包(SDK)或其他环境在Java中编写脚本。
  3. 嵌入式系统 : 从微型芯片到专用计算机的嵌入式系统是执行专门任务的大型机电系统的组件。诸如SIM卡,蓝光光盘播放器,公用事业仪表和电视机等多种设备都使用嵌入式Java技术。据甲骨文公司称,100%的蓝光光盘播放器和1.25亿台电视设备都采用Java技术。
  4. Web应用程序 : Java通过Servlets,Struts或JSP提供对Web应用程序的支持。编程语言提供的简单编程和更高的安全性使得大量政府应用程序可用于基于Java的健康,社会安全,教育和保险。Java也可以使用Broadleaf等开源电子商务平台开发电子商务Web应用程序。
  5. Web服务器和应用程序服务器 : 今天的Java生态系统包含多个Java Web服务器和应用程序服务器。虽然Apache Tomcat,Simple,Jo !, Rimfaxe Web服务器(RWS)和Project Jigsaw占据了Web服务器空间,但WebLogic,WebSphere和Jboss EAP在商业应用服务器领域占据重要地位 。
  6. 企业应用程序 : Java企业版(Java EE)是一种流行的平台,为脚本和运行企业软件(包括网络应用程序和Web服务)提供API和运行时环境。甲骨文宣称Java在97%的企业计算机上运行。Java中更高的性能保证和更快的计算能力导致像Murex这样的高频交易系统被编入脚本中。它也是各种银行应用程序的中枢,它们将Java从前端用户端运行到后端服务器端。
  7. 科学应用 : Java是许多软件开发人员用于编写涉及科学计算和数学运算的应用程序的选择。这些程序通常被认为是快速和安全的,具有更高的便携性和低维护性。像MATLAB这样的应用程序使用Java来交互用户界面和作为核心系统的一部分。

Java所带来的问题

整体上

并不是所有的工程和环境需要企业等级的复杂性,比如一个简单的个人网站或者独自编程的程序师所写的程序。这些程序师会发现Java的复杂管理对于自己要做的程序来说过于强大了。一些人觉得Java在面向对象上面做的没有 RubySmalltalk 纯粹。但是最新出现的用Java实现的语言 Groovy 解决了这些问题。

语言上

Java是一种 单继承 的语言。这也导致了程序师在试图使用 多重继承 时候的不便,而很多语言都可以使用这个特性。但是Java可以使用 接口类 ,把多重继承可能导致的风险减少到最小。Java不支持 运算符重载 ,这是为了防止 运算符重载 使得代码的功能变得不清晰。但是用Java实现的语言 Groovy 可以进行 运算符重载 。过去Java对于文本的操作和其他语言,比如 PerlPHP 相比差的较多,但Java在1.4版本时候引入了 正则表达式

性能上

Java语言的一些特性不可避免的有额外的性能代价,例如数组范围检查、运行时类型检查等等。Java程序的性能还会因为不同的动态复杂性和垃圾处理机制使用的多少而各有不同。如果JVM的实现比较优化的话,那么这些功能甚至可以增加存储器分配的性能。这和总是使用STL或者托管C++的程序的情况类似。
Java的设计目的主要是安全性和可携性,所以对于一些特性,比如对硬件架构和存储器地址访问的直接访问都被去除了。如果需要间接调用这些底层功能的话,就需要使用 JNI (Java本地接口)来调用本地代码,而间接访问意味着频繁调用这些特性时性能损失会很大,微软的.NET平台也有这样的问题。

Java语言特性

面向对象语言的特性

跨平台性

跨平台使得用Java语言编写的程序可以在编译后不用经过任何更改,就能在任何硬件设备条件下运行。这个特性经常被称为“一次编译,到处运行”。

自动垃圾回收(Garbage Collection)

C++语言被用户诟病的原因之一是大多数C++编译器不支持 垃圾收集 机制。通常使用C++编程的时候,程序员于程序中初始化对象时,会在主机 存储器 堆栈 上分配一块存储器与地址,当不需要此对象时,进行析构或者删除的时候再释放分配的存储器地址。如果对象是在堆栈上分配的,而程序员又忘记进行删除,那么就会造成 存储器泄漏 (Memory Leak)。长此以往,程序运行的时候可能会生成很多不清除的垃圾,浪费了不必要的存储器空间。而且如果同一存储器地址被删除两次的话,程序会变得不稳定,甚至崩溃。因此有经验的C++程序员都会在删除之后将指针重置为NULL,然后在删除之前先判断指针是否为NULL。
当一个对象没有任何引用的时候,Java的自动垃圾收集机制就发挥作用,自动删除这个对象所占用的空间,释放存储器以避免存储器泄漏。

接口和类别

Java自带了创建接口的类别,可以这样使用:

1
2
3
4
public interface Deleteable {
void delete();
System.out.println(" ATTACH DATABASE 'DatabaseName' As 'Alias-Name'");
}

这段代码的意思是任何实现(implement)Deleteable接口的类别都必须实现delete()方法。每个类别对这个方法的实现可以自行定制。由此概念可以引出很多种使用方法,下面是一个类别的例子:

1
2
3
4
5
6
7
8
9
10
11
12
public class Fred implements Deleteable {
// 必須實作Deleteable介面中的delete方法
@Override
public void delete() {
// 實作的程式碼
}

// 這個類別也可以包含其他方法
public void doOtherStuff() {

}
}

之所以这样做就是为了在接口的执行和其代码之间进行区别。举例来说,一个名叫Collection的接口可以包含任何对象所需要的引入、转换和存储数据的方法,其他的类都可以使用这个接口。但是这个接口可以是一个可重定义大小的队列、一个 链表 或者是其他功能的集合。
这种特性其实是一种折中的办法。Java的设计者们不想让Java有 多重继承 的特性,因为C++的多重继承显示了这种特性的困难。Java的接口功能可以提供同样的功能,但是又不会很复杂。

回顾知识

这里阐述两个知识,一个是参数传递,还有一个是指针。

参数传递方式

用函数调用所给出的实参(实际参数,actual arguments)向函数定义给出的形参(形式参数,formal arguments)设置初始值的过程,叫做参数传递(Argument Passing)。

C、C++语言的3种参数传递方式

调用函数时有三种参数传递方式:
1. 传值调用;
2. 传址调用(传指针);
3. 引用传递;
总体上参数传递可分为两类,由形参的类别决定:值调用(call by value)和引用调用(call by reference)。 这里传址调用被归为值调用,因为指针传递也是值传递,只不过值传递的是地址。

被调用函数的形参只有函数被调用时才会临时分配存储单元,一旦调用结束占用的内存便会被释放。
值传递和地址传递,传递的都是实参的一个拷贝。
实参和形参之间的数据传递是单向的“值传递”,单向传递,只能由实参传给形参,反之不行。

值传递(包括地址传递)
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
/*传值方式*/
#include<iostream>
using namespace std;

void swap(int m, int n)
{
int temp;
temp=m; m=n; n=temp;
}

int main()
{
int a,b;
cin>>a>>b;
swap(a,b);
cout<<a<<endl<<b<<endl;
}
//输入a = 1,b = 2,输出仍为 a = 1, b = 2。

/*传地址方式,形参影响实参*/
#include<iostream>
using namespace std;

void swap(int *m, int *n)
{
int temp;
temp = *m;
*m = *n;
*n = temp;
}

int main()
{
int a,b,*p1,*p2;
cin>>a>>b;
p1 = &a; p2 = &b;
swap(p1,p2);
cout<<a<<endl<<b<<endl;
}
//输入a = 1,b = 2,输出为 a = 2, b = 1。
//在该次中传递的是a,b的地址,在swap中,p1,p2分别表示指向a,b地址的地址,即*m,*n表示a,b的地址,是真正的实现了地址交换,所以主函数输出时实现了交换。

//如果不影响实参,可以在函数实现中这么写,指针传递实则传递地址,地址值在形参中的改变并不影响实参
int *temp;
temp = m;
m = n;
n = temp;
引用传递

传递的是值的引用,也就是说传递前和传递后都指向同一个引用(也就是同一个内存空间)。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
#include<iostream>
using namespace std;

void swap(int& m, int& n) //定义引用型的函数,
{ //对形参的操作相当于对实参的操作
int temp;
temp = m;
m = n;
n = temp;
}

int main()
{
int a,b;
cin>>a>>b;
swap(a,b);
cout<<a<<endl<<b<<endl;
}
  • 传递引用给函数与传递指针的效果是一样的, 形参变化实参也发生变化(需要看函数实现,说法有些不严谨)
  • 引用类型作形参,在内存中并没有产生实参的副本(共用一个存储单元) ,它直接对实参操作 ;而一般变量作参数,形参与实参就占用不同的存储单元, 所以形参变量的值是实参变量的副本因此当参数传递的数据量较大时,用引用比用一般变量传递参数的时间和空间效率都好
  • 指针参数虽然也能达到与使用引用的效果,但在被调用函数中需要使用 “*指针变量名” 的形式进行运算,容易出错,可阅读性差 ;另一方面,在主调函数的调用点处,必须用变量的地址作为实参。

Java语言中的参数传递

  • java中方法的参数传递方式只有一种:值传递;
  • 对于基本类型来说,传递的是实参的副本(值传递),故在方法内修改传递进来的值并不会影响实参本身;
  • 对于引用类型来说, 传递进来的是引用变量的副本(也是值传递),因此该副本与实参均是引用变量,他们均可以操作所引用的对象,在方法内通过引用变量对堆区的对象进行操作时均会对该对象有影响。 由于传入方法的是实际参数值的副本,因此, 参数本身不会受到任何影响
    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
    package practice;
    import java.util.Arrays;

    public class SelectionSort{
    public static void main(String [] args) {
    int[] a=new int[5];
    int n=a.length;
    for(int i=0;i<n;i++) {
    a[i]=(int)(Math.random()*100);
    }
    System.out.println("原数组是:"+Arrays.toString(a));
    WrongSwap(a[0],a[1]);
    System.out.println("WrongSwap后:"+Arrays.toString(a));
    swap(a,0,1);
    System.out.println("swap后:"+Arrays.toString(a));
    }
    static void swap(int[]a,int i,int j) {
    int temp;
    temp=a[i];
    a[i]=a[j];
    a[j]=temp;
    }
    static void WrongSwap(int b,int c) {
    int temp;
    temp=b;
    b=c;
    c=temp;
    }
    }
    /*
    原数组是:[14, 82, 75, 66, 62]
WrongSwap后:[14, 82, 75, 66, 62]
swap后:[82, 14, 75, 66, 62]
*/
    之所以WrongSwap没有达到交换元素的目的,是因为b,c是基本类型,调用WrongSwap传进去的是a[0],a[1]的值的拷贝,函数调用对它们并没有影响;
    而swap达到了交换元素的目的是因为,int[ ]a是引用类型,在参数传递的过程中,系统复制了引用变量a的副本传进方法,由于a只是一个引用变量,故实际操作的还是堆内存中的数组对象.

指针

  • 在 计算机科学 中,指针(英语:Pointer),是 编程语言 中的一类数据类型及其 对象 或 变量 ,用来表示或存储一个 存储器地址 ,这个地址的直接指向(points to)存在该地址的对象的。
  • 指针参考(reference)了存储器中一个地址。通过被称为指针反参考(dereferencing)的动作,可以取出在那个地址中存储的。保存在指针指向的地址中的,可能代表另一个 变量 、 结构 、 对象 或 函数 。但是从指针值是无法得知它所引用的存储器中存储了什么数据类型的信息。
  • 在计算机科学中,指针是一种最简单形式的 引用 (reference)。
  • 指针有两种含义,一是作为数据类型,二是作为实体。前者如字符指针、浮点指针等等;后者如指针对象、指针变量等。
  • 指针作为数据类型,可以从一个函数类型、一个对象类型或者一个不完备类型中导出。从中导出的数据类型称之为被引用类型(referenced type)。指针类型描述了一类的对象,对象值为对被引用类型的实体的引用。
  • C++标准中规定,“指针”概念不适用于 成员指针 (不包含指向静态成员的指针)。
    • object pointer type:指向void或对象类型,表示对象在内存中的字节地址或 空指针
    • function pointer type:指代一个函数
  • 取值运算(*p)返回保存在内存地址为p的内存空间中的值。取地址(&p)运算则返回操作数p的内存地址。 显然可以用赋值语句对内存地址赋值。

指针的复杂形式

双重指针(指向指针的指针)

双重指针是指向指针的指针,它是一个指针,这个指针指向某个内存地址,该地址的值是一个指针,指向给另一个内存地址(通常异于前者,但不排除二者相等)。
本质上,指针值就是内存地址。但为了防范指针值被滥用(如内存访问时越界),可以规定指针类型为强类型,即指针值及保存在该内存地址的对象的类型。双重指针不过是这种强类型的一个应用:该地址空间长度为一个指针长度(4或8字节),对象类型为另一种指针。

指针数组

指针 数组 :就是一个数组,数组的各个元素都是指针值。

数组指针

数组名出现在 表达式 中时,绝大多数情况(除了数组名作为sizeof的操作数或者作为取地址&元素符的操作数)会被隐式转换为指向数组的首个元素的指针 右值
当数组名作为取地址&运算符的操作数,则表达式的值为指向整个数组的指针右值。

1
2
3
4
5
6
7
char s[]="hello";

int main() {
char (*p1)[6]=&s; //OK!
char (*p2)[6]=s; //compile error: cannot convert 'char*' to 'char (*)[6]'
char *p3=&s;//compile error: cannot convert 'char (*)[6]' to 'char*'
}
指向函数的指针

指向函数的指针:不同于指向数据类型的指针,函数指针指向一段可执行的代码的首地址,这段代码仍然占用了一块内存空间。很多人都说C语言是一种 面向过程 的语言,因为它最多只有 结构体 的定义,而没有的概念。根据本段所述,可以认为C语言能成为 面向对象 的语言,只是表述比较麻烦而已。 事实上很多 开源 程序都使用这种方式组织他们的代码。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>

void inc(int *val)
{
(*val)++;
}

int main(void)
{
void (*fun)(int *);
int a=3;
fun=inc;
(*fun)(&a);
printf("%d", a);
return 0;
}
指针运算符的重载

他们与Objective-C

Objective-C是C语言的严格超集--任何C语言程序不经修改就可以直接通过Objective-C编译器,在Objective-C中使用C语言代码也是完全合法的。Objective-C被描述为盖在C语言上的薄薄一层,因为Objective-C的原意就是在C语言主体上加入 面向对象 的特性。Objective-C的 面向对象 语法源于 Smalltalk 消息传递风格。所有其他非 面向对象 的语法,包括变量类型,预处理器(preprocessing),流程控制,函数声明与调用皆与C语言完全一致。但有些C语言语法合法代码在objective-c中表达的意思不一定相同,比如某些布尔表达式,在C语言中返回值为true,但在Objective-C若与yes直接相比较,函数将会出错,因为在Objective-C中yes的值只表示为1.
Objective-C是非常“实际”的语言。它用一个很小的、用C写成的 运行库 ,使得应用程序的大小增加很少,与此相比,大部分OO系统需要极大的运行时虚拟机来执行。ObjC写成的程序通常不会比其源代码和库(通常无需包含在软件发行版本中)大太多,不会像Smalltalk系统,即使只是打开一个窗口也需要大量的容量。由于Obj-C的动态类型特征,Obj-C不能对方法进行内联(inline)一类的优化,使得Obj-C的应用程序一般比类似的C或C++程序更大。
Obj-C可以在现存 C编译器 基础上实现(在GCC中,Obj-C最初作为 预处理器 引入,后来作为模块存在),而不需要编写一个全新的编译器。这个特性使得Obj-C能利用大量现存的C代码、库、工具和编程思想等资源。现存C库可以用Obj-C包装器来提供一个Obj-C使用的OO风格界面包装。
以上这些特性极大地降低了进入Obj-C的门槛,这是1980年代Smalltalk在推广中遇到的最大问题。

OC的和C++的区别

  1. 继承:Objective-C与不支持多重继承,而C++语言支持多重继承(从侧面可以说明多重继承的效率不高);
  2. 函数调用:Objective-C通过互相传递消息实现函数调用,而C++直接进行函数调用
  3. 定型:Objective-C是动态定型。所以它的类库比C++要容易操作。Objective-C 在运行时可以允许根据字符串名字来访问方法和类,还可以动态连接和添加类。而C++,对象的静态类型决定你是否可以发送消息给它。
  4. 接口:Objective-C采用protocol协议(非正式和正式)的形式来定义接口,而C++采用虚函数的形式来定义接口。
  5. 方法重载:c++中允许两个方法的名字相同,参数个数相同,但是参数类型不同,以及不同的返回值类型。而OC中不允许同一个类中两个方法有相同的名字,参数个数相同,参数类型不同。

编译

一般可以将编程语言分为两种, 编译语言直译式语言
像C++,Objective C都是编译语言。编译语言在执行的时候,必须先通过编译器生成机器码,机器码可以直接在CPU上执行,所以执行效率较高。
像JavaScript, Python 都是直译式语言。直译式语言不需要经过编译的过程,而是在执行的时候通过一个中间的解释器将代码解释为CPU可以执行的代码。所以,较编译语言来说,直译式语言效率低一些,但是编写的更灵活,也就是为啥JS大法好。
iOS开发目前的常用语言是:Objective和 Swift 。二者都是编译语言,换句话说都是需要编译才能执行的。二者的编译都是依赖于Clang + LLVM. 篇幅限制,

更多的拓展:iOS编译过程的原理和应用_移动开发_一头雾水的Blog-CSDN博客

CATALOG
  1. 1. 回顾C、C++、Java
    1. 1.1. C语言
      1. 1.1.1. C语言具有的特性
      2. 1.1.2. C语言的优点
      3. 1.1.3. C语言的缺点
    2. 1.2. C++语言
      1. 1.2.1. C++语言的设计原则
      2. 1.2.2. C++语言中的特色
      3. 1.2.3. C++与C之间的不兼容
      4. 1.2.4. C++语言的几大特性
        1. 1.2.4.1. 模板
        2. 1.2.4.2. 类别与对象
        3. 1.2.4.3. 封装
        4. 1.2.4.4. 继承
          1. 1.2.4.4.1. C++支持多继承(multiple inheritance,MI)
        5. 1.2.4.5. 多态
          1. 1.2.4.5.1. 静态多态中的函数重载
          2. 1.2.4.5.2. 动态多态中的虚函数
    3. 1.3. Java语言
      1. 1.3.1. Java主要用途
      2. 1.3.2. Java所带来的问题
        1. 1.3.2.1. 整体上
        2. 1.3.2.2. 语言上
        3. 1.3.2.3. 性能上
      3. 1.3.3. Java语言特性
        1. 1.3.3.1. 面向对象语言的特性
        2. 1.3.3.2. 跨平台性
        3. 1.3.3.3. 自动垃圾回收(Garbage Collection)
        4. 1.3.3.4. 接口和类别
    4. 1.4. 回顾知识
      1. 1.4.1. 参数传递方式
        1. 1.4.1.1. C、C++语言的3种参数传递方式
          1. 1.4.1.1.1. 值传递(包括地址传递)
          2. 1.4.1.1.2. 引用传递
        2. 1.4.1.2. Java语言中的参数传递
      2. 1.4.2. 指针
        1. 1.4.2.1. 指针的复杂形式
          1. 1.4.2.1.1. 双重指针(指向指针的指针)
          2. 1.4.2.1.2. 指针数组
          3. 1.4.2.1.3. 数组指针
          4. 1.4.2.1.4. 指向函数的指针
          5. 1.4.2.1.5. 指针运算符的重载
    5. 1.5. 他们与Objective-C
      1. 1.5.1. OC的和C++的区别
      2. 1.5.2. 编译