From Swift To C++

栏目: C++ · 发布时间: 5年前

内容简介:C++ 除了在操作系统内核,编译器等高性能领域中发挥着关键作用,同时也是跨平台开发中不可或缺的角色。值得对底层优化和跨平台开发有兴趣的同学了解与学习。Swift 和 C++ 初看起来是两种差异较大的语言。但是随着逐步深入了解,我们会发现他们有一个最大的共同点,那就是多范式编程。

From Swift To C++

姊妹篇: 从 Objective-C 到 Swift

C++ 除了在操作系统内核,编译器等高性能领域中发挥着关键作用,同时也是跨平台开发中不可或缺的角色。值得对底层优化和跨平台开发有兴趣的同学了解与学习。

Swift 和 C++ 初看起来是两种差异较大的语言。但是随着逐步深入了解,我们会发现他们有一个最大的共同点,那就是多范式编程。

这篇文章就按照编程范式(programming paradigm)来组织脉络(非严格划分,事实上不同编程范式都会用到许多相同的语法特性),下面就让我们从一个客户端工程师的角度来品味和对比这两门语言。

目录:

    • 控制流
    • 函数
    • 封装
    • 继承
    • 多态
    • 访问控制
    • 消息传递机制
    • 强类型,静态类型
    • 类型推导
    • 值语义 引用语义
    • 类型转换
    • 智能指针
    • Optional
    • 实现模型
    • 泛型函数
    • 泛型类型
    • 类型约束
    • 闭包

面向过程(Procedure Oriented Programming)

控制流(Control Flow )

Selection:

C++ 拥有 if switch 语法。

而Swift除了拥有 ifelse , 还有 gurad 用于提前返回。

Loop:

C++ 拥有 do while while for(::) range-based for

而Swift则拥有 repeat while whilefor in . ​ 值得一提的是, Swift从 3.0 版本已经去掉了 C-Style 的 for 循环 ​ 而 C++11 加入的 range-based for 和 Swift 的 for in 基本是异曲同工的。 ​

std::map<string, int> testMap;

for (auto& item : testMap)
{
    cout << item.first << ":" << item.second << endl;
}
vat map: Dictionary<String:Int>;
for (key,value) in map {
}

另外 c++ 的 switch 能力也比较弱,只能够对整型,枚举或一个能隐式转换为整型或枚举类型的class进行判断. 而 Swift 的 switch 能力强大得多,基本能够判断所有类型,包括字符串,浮点数等等。

函数

C++ 的函数定义语法和 C 是一脉相承的。

C++ 和 Swift 一样,也支持指定参数的默认值(Default Parameter Values):

int sum(int a, int b=20)
{
  int result;
 
  result = a + b;
  
  return result;
}

不过 C++ 因为缺少像 Swift 的 Function Argument Labels , 因此定义方法需要遵循:若给某一参数设置了默认值,那么在参数表中其后所有的参数都必须也设置默认值

调用时需要:若给已经设置默认值的参数传递实际值,则在参数表中被取代参数的左边所定义的所有参数,无论是否有默认值,都必须传递实际参数。

 void func(int a=1,int b,int c=3, int d=4); //error
 void func(int a, int b=2,int c=3,int d=4); //ok
 //不能直接选择给 a 和 c 赋值
 func(2,15,20);

而 Swift 则可以灵活地选择给哪个参数赋值:

func test(a :Int = 1, b:Int, c:Int = 3, d:Int = 4 ) {}
test(b:3, d: 5);

C++ 的函数初看没有什么可以挖掘的地方,但是当涉及面向对象,模板(泛型编程)时,就会有更多强大的语法特性显现出来。让我们继续耐心阅读下去。

参考链接

Swift Control Flow

Range-based for loops

面向对象 (Object Oriented Programming)

封装

  • 定义class:

    class Person {
       public:
         int x;
         void foo();
     	
       private:
         float z;
         void bar();
     }
  • 构造函数和析构函数(Constructors and Destructors)

    相当于Swift的Initialization和Deinitialization。

    class Foo {
      private:
        int x;
    
      public:
        Foo() : x(0) {
        }
    
        Foo(int x) : x(x) {
        }
        //上下两个构造方法是等价的。
        Foo(int x) {
        	this->x = x;
        }
    };

    注意到C++有个特殊构造方法语法叫 Initialization Lists ,用于初始化成员变量。

  • 变量初始化语法:

    //小括号初始化
    string str("hello");
    
    //赋值初始化
    string str = "hello";
    
    //花括号初始化  c++11后 都推荐这种初始化方法 uniform initialization
    vector<int> vec = {1,2,3}; //c++ 11 独有的  

    uniform initialization

​ ​ C++和Swift有个很大的区别,那就 C++ 如果定义变量时没有指定初值,则变量被默认初始化(default initialized),也就是会被自动赋值。个人觉得这不是一个好的语言特性, 最好还是像 Swift 一样强制需要显式初始化,这样代码不隐晦,一看就知道真正的初始化值。

继承

//Swift
class Person {}
class Employee: Person {}
//C++
class Person {
};
	
class Employee : public Person {
};

注意到 C++ 多了一个 public 关键词,这代表了 Person 类的 public members 在Employee 中还是 public 的。如果将 public 替换成 private ,则外界调用 Employee 时,父类 Person 的 public members 是不可见的。 ​ C++ 支持多继承。而 Swift 则可以通过 Protocol 和 Protocol Extension 来实现类似多继承的特性。

多态(Polymorphism)

我们来看看下面这段代码:

class Foo {
  public:
    int value() { return 5; }
};
	
class Bar : public Foo {
  public:
    int value() { return 10; }
};
	
Bar *b = new Bar();
Foo *f = (Foo*)b;
printf(“%i”, f->value());
// Output = 5

我们惊奇地发现,返回值是5,这和 Swift 的行为是不同的。这在 C++ 叫做 Static Binding。方法的调用在编译期就确定了。我们需要利用 C++ 的语法特性 virtual function 来实现多态。让方法的调用在运行时确定(dynamic binding)。 ​

class Foo {
  public:
    virtual int value() { return 5; }
};
	
class Bar : public Foo {
  public:
    virtual int value() override { return 10; }
};
	
Bar *b = new Bar();
Foo *f = (Foo*)b;
printf(“%i”, f->value());
// Output = 10

类似于 Swift 的 Protocol。在C++中,我们是通过 pure virtual function (or abstract function)来定义接口的。 ​

class Base
{
   int x;
public:
	//pure virtual function
    virtual void fun() = 0;
    int getX() { return x; }
};
 
// This class ingerits from Base and implements fun()
class Derived: public Base
{
    int y;
public:
	//c++11 用override显式表示重载虚函数
    virtual void fun() override { cout << "fun() called"; }
};

访问控制(Access Control):

c++也有访问控制,分为 public , private , protected , friend

区别在于Swift是没有 protected 的, protected 指子类也能访问和修改。

friend 是一个比较特别的控制语法,大多数编程语言都没有这个特性,C++中的友元机制允许类的非公有成员被一个类或者函数访问。

值得注意的是:

  1. 在C++中,class 的默认访问控制是 private ,而 struct 或 union 则是 public

  2. 继承也需要指定访问控制。

``c++
class Derived : Base  
//上下是等价的。
class Derived : private Base  
```

如果不指定 public, 我们是无法在子类中使用父类的方法的。

参考链接:

苹果对于为什么不支持protected的看法

消息传递机制 (Method Dispatch/Message passing)

C++ 和 Swift 都有 static dispatch 和 dynamic dispatch 两种消息传递机制。 ​ 对于前者,Swift 和 C++ 都是编译时(compilation time)就确定了调用地址。 ​ 对于后者,Swift 的 dynamic dispatch 有两种形式,一种是通过Objective-C的runtime进行分发。一种是通过和C++类似的 vtable 进行分发。 ​ 除了标记为 final , private , @objc dynamic 的方法。Swift的方法都类似C++标记了 vitrual 的方法。

How does iOS Swift Runtime work

Dynamic Dispatch in Object Oriented Languages

类型系统(Type System)

强类型,静态类型

和Swift一样,C++也是一个强类型(strongly typed),静态类型(statically typed)的编程语言。

从语法的角度来看:

强类型:一旦变量被指定某个数据类型,如果不经转换,即永远是此数据类型。

静态类型:变量类型在编译时就确定. ​ 静态类型又可以细分为: manifest typetype-inferred 语言。 manifest type 需要我们显式指定变量的类型,如:int a。 而 type-inferred 则可以由编译器来帮我们推导类型。

类型推导 (Type Inference)

C++ 和 Swift 都具备类型推导的能力。

//c++
auto a = 1;
//swift
let a = 1

C++ 的类型推导能力还能用在函数的返回类型中:

//c++14起支持
template<class T, class U>
auto add(T t, U u) { return t + u; } 
//the return type is the type of operator+(T, U)

值语义 引用语义 (value semantics and reference semantics)

C++ 不像 Swift 将类型明确分为 Reference Type 和 Value Type 。而是通过指针和引用来实现引用语义。 在C++中,classes 默认是 value types.

class Foo {
  public:
    int x;
};
	
void changeValue(Foo foo) {
    foo.x = 5;
}
	
Foo foo;
foo.x = 1;
changeValue(foo);
// foo.x still equals 1

需要指定pass a variable “by reference”. ​

void changeValue(Foo &foo) {
    foo.x = 5;
}
	
Foo foo;
foo.x = 1;
changeValue(foo);
// foo.x equals 5

类型转换 ( Type Conversions )

  1. 隐式类型转换 ( Implicit type conversions )

    Swift是没有隐式类型转换的,而C++有。

    //成立
    float a = 1, double b = a; 
    //精度损失,会有warning
    double a = 1, float b = a;

    个人不是很赞同隐式类型转换,我认为一个强类型语言的所有类型转换都应该是显式的。这样更统一和规范,也许隐式类型转换能够带来一点编写代码的便利性,但也隐藏了问题,特别是有精度损失的隐式转换。也许最好的做法是保留隐式类型转换,但是只允许 Widening conversions ,也即提高精度的转换。

  2. 显示类型转换 ( Explicit conversions )

    现代C++通过 static_castdynamic_cast , const_cast 来进行显式类型转换。

    dynamic_cast 就类似Swift的 as? , 是安全的类型转换操作。

    Base* b = new Base();  
    // Run-time check to determine whether b is actually a Derived*  
    Derived* d3 = dynamic_cast<Derived*>(b);  
    // If b was originally a Derived*, then d3 is a valid pointer.  
    if(d3)  
    {  
    	d3->DoSomethingMore();
    }

参考链接:

C++值语义

面向对象编程中引用和const的结合

类型系统

内存管理

智能指针

myPerson = nullptr;
myPerson->doSomething(); // crash!

在传统 C++里,一般用 newdelete 这两个语法进行内存管理,稍有不慎就会导致内存泄露等问题。 好在C++11也引入了引用计数进行内存管理。具体的语法关键词是使用 std::shared_ptrstd::weak_ptrunique_ptr

unique_ptr : 只能有一个unique指针指向内存, 不存在多个unique指针指向同块内存

unique_ptr<T> myPtr(new T);       // Okay
unique_ptr<T> myOtherPtr = myPtr; // Error: Can't copy unique_ptr

shared_ptr

// 初始化
shared_ptr<int> y = make_shared<int>();
shared_ptr<Resource> obj = make_shared<Resource>(arg1, arg2); // arg1, arg2是Resource构造函数的参数

weak_ptr

C++中提供了lock函数来实现该功能。如果对象存在,lock()函数返回一个指向共享对象的 shared_ptr ,否则返回一个空 shared_ptr

auto sharedPtr = make_shared<XXXDelegate>();
auto delegate =  weak_ptr<XXXDelegate>(sharedPtr);
if (auto unwrap = delegate->lock()) {
    unwrap->XXX();
}

C++ 开发者在使用智能指针的过程中总结出四句原则:

  • shared_ptr ,不用 new

  • 使用 weak_ptr 来打破循环引用

  • make_shared 来生成 shared_ptr

  • 继承 enable_shared_from_this 来使一个类能获取自身的 shared_ptr

在自己实际开发的过程中,还总结出一个:如果一个对象在整个生命周期中会需要被智能指针所管理,那么一开始就要用智能指针管理。

参考链接:

cpp-dynamic-memory

shared_ptr 原理及事故

Optional:

optional<int> o = str2int(s); // 'o' may or may not contain an int
if (o) {                      // does optional contain a value?
  return *o;                  // use the value
}

c++的判断不是编译期强制的。没有像Swift一样的 unwrap 语法。还是需要自己判空,和指针判空类似,但是 Optional 的优点是能够表达一个非指针的对象是否为空。

泛型编程

C++ 有着比 Swift 更强大的泛型编程能力,但是代价就是语法和代码会更加晦涩。

实现模型(Implementation Model)

实际上 Swift 的泛型和 C++ 的泛型的实现模型有着本质区别。C++ 的泛型(模板)是在编译期生成每个类型具体的实现。而 Swift 则是利用类型信息和 Swift runtime来实现。 这个话题非常宏大艰深,涉及到编译器的底层细节,有兴趣的读者可以加以研究并分享。

在这里我们简单通过一个泛型函数来简单感受一下:

template <typename T>
T f(T t) {
    T copy = t;
    return copy;
}

f(1);
f(1.2);

c++ 的编译时会生成两份代码:

int f(int t) {
    int copy = t;
    return copy;
}

float f(float t) {
	float copy = t;
	return copy;
}

而 Swift:

func f<T>(_ t: T) ->T {
	let copy = t
	return copy
}

编译器实现则类似以下,不会为每个类型生成单独一份实现。

void f(opaque *result,opaque *result,type *T) {
	//vwt: value witness table
	//利用类型信息来实现
	T->vwt->XXX(X);
}

泛型函数

Swift:

func genericSwap<T>(inout a:T,inout _ b:T){
    let temp = a
    a = b
    b = temp
}

C++:

template <typename T>
void swap(T &a, T &b) {
    T temp = a;
    a = b;
    b = temp;
}

泛型类型

template <typename T>
class Triplet {
  private:
    T a, b, c;

  public:
    Triplet(T a, T b, T c) : a(a), b(b), c(c) {}

    const T& getA() { return a; }
    const T& getB() { return b; }
    const T& getC() { return c; }
};
Triplet<int> intTriplet(1, 2, 3);
Triplet<float> floatTriplet(3.141, 2.901, 10.5);

c++通过在方法或类型前面定义 template <typename T> ,来定义类型参数( type parameter

在Swift中泛型类型我们需要通过 typealias 暴露类型参数给外面。 在C++中,需要用typedef暴露给外面使用。

template <typename Reqest,typename Response>
class kindaBaseCgi {
public:   
    typedef Reqest RequestType;
    typedef Response ResponseType;
 }

类型约束

Swift有类型约束( Type Constraints )来约束类型参数继承某个类或遵循某个协议。

目前C++中,没有特别的语法来实现这个效果。我们需要借助 static_assert 在编译中检查类型:

template<typename T>
class YourClass {
    YourClass() {
        // Compile-time check
        static_assert(std::is_base_of<BaseClass, T>::value, "type parameter of this class must derive from BaseClass");
    }
}

在编写 C++ 模板代码的过程中,其实我觉得类型约束这个的重要性并不大。这归结于我们上面提到的语言对泛型的实现模式的不同。

考虑到如下的模板函数:

template <typename T>
T test(T a, T b) {
    return a + b;
}

test(1,2); //编译的时候,会为 int 生成实现

int test(int a, int b) {
    return a + b;
}

//而这个实例化(instantiation)后的函数是正确的,所以编译成功

而在 Swift 中:

//编译报错:Binary operator '+' cannot be applied to two 'T' operands
func test<T>(a:T, b:T) -> T {
    return a + b
}

Swift 因为需要靠类型信息和 runtime 来实现泛型。因此它需要知道 T 这个类型能够进行 + 操作。

//编译成功
func test<T:Numeric>(a:T, b:T) -> T {
    return a + b
}

C++ 编译成功与否决定于模板实例化的时候。这一特性非常强大,我们不需要一开始就提供所有信息给编译器。直到实例化时,编译器才会检查实现是否正确,是否该类型能够支持 + 操作。

C++ 还有一个Swift没有的强大特性,那就是 SFINEA 。 同时也引入了一个新的编程范式。那就是 Compile-time Programming ,这里暂不展开。

参考链接:

Metaprogramming as a non-goal

Implementing Swift Generics

c++20类型约束有望得到提升

函数式编程

C++11 后,引入了闭包概念,使得 C++ 的函数式编程变得更简单清晰。

闭包(Closures)

在C++, 闭包被称为 lambda .

auto y = [] (int first, int second)  
{  
    return first + second;  
};  

//显式声明
function<int(int, int)> f2 = [](int x, int y) { return x + y; };

一个简单的闭包语法:

[ captures ] ( params ) -> ret { body }
  • Capturing Values:

在Swift中,闭包能够自动帮助我们捕获values。

let i = 10
let closure = { print(i) }
closure()

而c++需要开发者显式指定捕获变量。

auto i = 1;
auto closure = [] { std::cout << i << std::endl;};
//error: 'i' is not captured
auto i = 1;
auto closure = [=] { std::cout << i << std::endl;};
closure();
//编译成功

参考链接:

C/C++语言中闭包的探究及比较

C++函数指针、函数对象与C++11 function对象对比分析

并发编程( Concurrency Programming)

Swift 目前没有语言级别的并发编程机制,在业务开发中,我们通常通过 Grand Central Dispatch(GCD) 进行并发编程,在此不再赘述。

从 C++11 开始,C++ 标准第一次承认多线程在语言中的存在,并在标准库中为多线程提供了组件。

我们可以通过这样的一个例子感受一下:

//Calling std::async with lambda function
std::future<std::string> resultFromDB = std::async([](std::string recvdData){
 
						std::this_thread::sleep_for (seconds(5));
						//Do stuff like creating DB Connection and fetching Data
						return "DB_" + recvdData;
 
					}, "Data");

但是深入使用,我们会非常 C++ 的并发编程使用起来会非常晦涩。比如并没有一个原生的线程池机制来保证性能和健壮性。也没有一个直观的机制在其他线程回调主线程,这在 GCD 中,只需要我们 disptach 到 mainQueue 即可。

针对客户端的跨平台开发,如果涉及到异步,Timer的时候,建议还是可以利用各个平台的上层组件。C++ 层负责定义接口,上层负责实现。

参考链接:

std::async Tutorial & Example

Swift Concurrency Manifesto


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

数据挖掘导论

数据挖掘导论

Pang-Ning Tan, Michael Steinbach / 范明 范宏建 / 人民邮电出版社 / 2006-01-01 / 49.00元

本书全面介绍了数据挖掘的理论和方法,旨在为读者提供将数据挖掘应用于实际问题所必需的知识。本书涵盖五个主题:数据、分类、关联分析、聚类和异常检测。除异常检测外,每个主题都包含两章:前面一章讲述基本概念、代表性算法和评估技术,后面一章较深入地讨论高级概念和算法。目的是使读者在透彻地理解数据挖掘基础的同时,还能了解更多重要的高级主题。此外,书中还提供了大量示例、图表和习题。 本书适合作为......一起来看看 《数据挖掘导论》 这本书的介绍吧!

JSON 在线解析
JSON 在线解析

在线 JSON 格式化工具

随机密码生成器
随机密码生成器

多种字符组合密码

正则表达式在线测试
正则表达式在线测试

正则表达式在线测试