美文网首页
重学iOS——1.从C角度看OC

重学iOS——1.从C角度看OC

作者: Peak_One | 来源:发表于2017-10-13 16:52 被阅读104次
    面向对象

    面向对象的编程思维,简而言之就是:一切皆对象。面向对象的语言区别于C语言(面向过程),C语言是按照代码的执行顺序去实现项目功能,而高级编程语言是从对象的角度去把控,详细解释就是:任何事件都是由事物构成,事物拥有自己特有的属性(对象的固有属性和行为属性),大事件通过各种事物之间的相互作用(行为调用)实现。OC是从C语言的基础之上发展而来的,是C语言的面向对象。OC完全兼容C语言,可以实现OC与C的混合编程,一起生成可执行文件。

    OC与C的部分不同点

    参考代码:

    #import <Foundation/Foundation.h>
    #import <objc/objc.h>
    
    /*
     *要求定义一个人类
     *事物名称:人
     *属性:年龄(age),身高(height),体重(weight)
     *行为:吃饭(eat),睡觉(sleep),散步(walk)
     */
    
    @interface Person : NSObject
    {
        @public
        //属性
        int _age;
        double _height;
        double _weight;
    }
    //行为
    -(void)eat:(char *)food;
    
    -(void)sleep;
    
    -(void)walk;
    +(void)about;
    @end
    
    
    @implementation Person
    
    -(void)eat:(char *)food
    {
        NSLog(@"吃%s",food);
    }
    
    -(void)walk
    {
        NSLog(@"开始遛弯");
    }
    
    -(void)sleep
    {
        NSLog(@"开始睡觉");
    }
    
    +(void)about
    {
        NSLog("Hello");
    }
    @end
    
    
    int main(int argc, const char * argv[]) {
        //1. 通过类创建对象
        /*
         *1.开辟存储空间
         *2.初始化所有属性
         *3.返回指针地址
         */
    
        //因为在堆栈中创建的对象,实质是一个结构体,所以可以通过结构体指针直接访问属性
        /*
         *创建对象的时候,返回的地址其实就是类的第0个属性的地址
         *结构体创建完成以后,结构体的地址其实就是结构体第0个成员的首地址
         *但是需要注意的是:类的第0个属性并不是我们编写的_age,而是一个叫做isa的属性
         *isa是一个指针,占8个字节
         *
         *其实类也是一个对象,也就意味着Person也是一个对象
         *平时我们所说的创建对象,就是通过一个类对象创建一个新的对象
         *类对象是系统自动帮我们创建的,里面保存了当前对象的所有方法,准确说是一个方法列表,包括方法体
         *而实际对象是程序自己员手动通过new来创建的,而实例对象中有一个isa指针就指向了创建它的那个类对象
         */
        Person *p=[Person new];
        p->_age=30;
        p->_height=1.75;
        p->_weight=65.0;
        [p walk];
        [Person about];
        NSLog(@"p=%p",p);
        NSLog(@"&age=%p",&(p->_age));
        /*
         *结构体创建完成以后,结构体的地址其实就是结构体第0个成员的首地址
         */
        //    struct Person
        //    {
        //        int age;
        //        char *name;
    //    };
    //    struct Person sp;
    //    NSLog(@"&sp=%p",&sp);
    //    NSLog(@"&age=%p",&sp.age);
        return 0;
    }
    

    1.#import和#include的区别
    import的功能和include的功能是一样的,都是导入文件,但是OC为了避免C语言中的重复导入问题(避免使用复杂的头文件卫士)为import集成了避免重复导入的机制。下面介绍一下C语言中头文件卫士的写法:

    #ifndef _TEST_H
    #define _TEST_H//一般是文件名的大写
    头文件结尾写上一行:
    #endif
    

    这样一个工程文件里同时包含两个test.h时,就不会出现重定义的错误了。
    分析:当第一次包含test.h时,由于没有定义_TEST_H,条件为真,这样就会包含(执行)#ifndef _TEST_H和#endif之间的代码,当第二次包含test.h时前面一次已经定义了_TEST_H,条件为假,#ifndef _TEST_H和#endif之间的代码也就不会再次被包含,这样就避免了重定义了。
    当然以上这些内容都是关于C语言-预处理的知识内容,具体细节参考:C语言再学习——C预处理器和头文件为什么要加#ifndef #define #endif

    2.OC实现面向对象的原理
    OC是对C语言进行了面向对象的封装,即通过C语言实现面向对象,那么是如何实现的?其实是结构体+指针的方式实现的。OC中的对象在内存中其实是以结构体的形式进行存储的。这一点的内容就涉及到了runtime运行时的内容。在这里由于目前还没有涉及到运行时内容,所以对本部分内容进行了适当的简化,同时也增进了后期运行时内容的进一步学习与理解。
    OC中的对象是通过结构体来实现的,即在堆中对象的存储数据结构是struct,创建的对象变量,其实是一个指向相应类结构体的指针。由于对象的实际结构是结构体,那么我们可以通过"->”这样的方式去访问实例对象中的属性,代码如下:

    IPhone *p=[IPhone new];
    p->_color=0;//默认@interface中的属性都是@protected的,如果想这样子直接访问,需要使用@public
    p->_model=0;
    

    在实际的实例对象存储过程中,实例对象内部是不存在实例方法的,只包含属性信息和isa指针信息,isa指针指向该类的类对象,类对象是在程序编译阶段,系统自动创建的,包含了类的所有信息(属性列表、方法列表、类名等信息),是创建实例对象的依据(模板)。需要注意的是:结构体创建完成以后,结构体的地址其实就是结构体第0个成员的首地址,但是类对象的首地址是isa指针属性的存储值,isa是一个指针,占8位。具体在内存中的存在方式如下图所示:

    对象创建过程.png

    所以从上图中我们可以联想到,[Person new]方法,其实是调用了Person类对象的new方法。参考代码中的[p walk]和 [Person about]对比会发现,类方法的执行效率高于实例方法。图解如下:

    [p walk]分析.png [Person about]分析.png

    两幅图的分析,故意将元类的概念略去,因为类方法实际是存储在元类中的,但是在此为了方便理解,同时也为后期进阶做准备。
    上图中涉及到了堆、栈、代码区等内容,具体可以参考iOS中的内存管理C语言中内存分配linux内存管理深入浅出-iOS内存分配与分区

    3.OC中的类方法和实例方法以及函数
    OC和C中的函数的对比,其实很简单,在这里直接上代码,我觉得对于身为程序员的自己来说,可能会更加清晰,注意点:在Java中对象可以直接调用类方法,但是在OC中对象不能调用类方法,必须通过类调用类方法。静态方法、类方法和对象方法的区别

    //
    //  main.m
    //  OC_day1_04_类方法
    //
    //  Created by 刘旭辉 on 2017/10/13.
    //  Copyright © 2017年 刘旭辉. All rights reserved.
    //
    
    #import <Foundation/Foundation.h>
    
    typedef enum
    {
        KIColorBlace,//黑色
        KIColorWhite,//白色
        KIColorTuHaoJin//土豪金
    }IColor;
    //1.编写类的声明
    @interface IPhone : NSObject
    {
    @public
        double _size;
    //    int _color; //颜色 0代表黑色 1代表白色 2代表土豪金
        int _cpu;
        int _model;
        IColor _color;
    }
    /*
     *行为
     *OC中的行为和C语言中的函数一样,都是用来保存一段特定功能的代码
     *OC中定义一个方法,也分为声明和实现,声明在@interface中,实现写在@implementation
     *
     *C语言中的函数分为两种:内部函数和外部函数
     *OC中的函数也分为两种:类方法和对象方法
     *类方法只能用类名调用,对象方法只能用对象调用
     *OC中的类方法用+表示,OC中的对象方法用-表示
     *
     *编写C语言函数的规律:1.确定函数名称;2.确定形参;3.确定返回值;4.确定返回值类型
     *编写OC方法也有规律,规律和C语言一模一样
     */
    
    /*C语言方法
     void about();
     void about()
     {
         printf("model=cpu=size=color");
     }
     */
    
    //注意:OC中的方法,如果没有形参不需要写(),而是直接写一个;
    //为什么OC中没有形参不需要写()呢?因为OC方法中的()有特殊用途,OC方法中的()是用来括住数据类型的
    //行为的声明
    -(void)about;
    
    /*
     *C语言中的函数:1.没有返回值没有参数的函数;2.有返回值没有参数的;3.有返回值有参数的;4.没有返回值有参数的
     *OC中的方法也有四种
     */
    
    //有返回值没有参数的。例如:读取短信
    -(char *)loadMessage;
    
    //有返回值有参数的,例如:打电话
    //int signal(int number); C语言中的函数
    //注意:OC中的方法如果有参数,那么每个参数的数据类型前面必须加一个:
    //注意:当前的有参数的方法的方法名称是  signal: 冒号也是方法名称的一部分
    -(int)signal:(int)number;
    
    //有返回值,并且有多个参数的,发短信
    //int sendMessage(int number,char* content);
    //为了提高阅读性,OC方法允许我们给每一个参数添加一个标签来说明当前参数的含义
    -(int)sendMessageWithNumber:(int)number andContent:(char*) content;
    //-(int)sendMessage:(int)number :(char*) content; //当前的方法名称叫做:sendMessage::
    
    //没有返回值,有参数的函数
    -(void)callWithNumber:(int) number;
    
    
    //计算器功能(类方法)
    //如果你不想每次使用方法都需要创建对象并且开辟存储空间
    //并且如果该方法中没有使用到属性(成员变量),那么你可以把这个方法定义为类方法
    //对象方法用对象调用 类方法用类名调用
    //-(int)sumWithNumber1:(int)number1 andNumber2:(int) number2;
    //只需要将对象方法的-号转换成+,那么就定义了一个类方法
    //注意:如果实现了一个类方法,就必须去实现类方法
    //如果实现的是一个对象方法,那么就必须去实现对象方法
    
    
    
    
    /*
     *类方法和对象方法的区别
     *0.对象方法以-开头;类方法以+开头
     *1.对象方法必须用对象调用,类方法必须用类调用
     *2.对象方法中可以直接访问属性(成员变量),类方法中不可以直接访问属性(成员变量)
     *3.类方法的优点,调用类方法的效率会比调用对象方法高
     *4.类方法和对象方法可以进行相互调用
     *4.1对象方法中可以直接调用类方法
     *4.2可以在类方法中间接调用对象方法
     *4.3在类方法中可以直接调用其他方法
     *4.4对象方法中可以直接调用对象方法
     */
    
    
    /*
     *类方法的应用场景
     *如果方法中没有使用到属性(成员变量),那么能用类方法就用类方法
     *类方法的执行效率比对象方法高
     *
     *类方法一般用于定义工具方法
     *字符串查找
     *文件操作
     *数据库操作
     */
    +(int)sumWithNumber1:(int)number1 andNumber2:(int) number2;
    @end
    
    //2.编写类的实现
    @implementation IPhone
    //行为的实现
    -(void)about
    {
        //    NSLog(@"打印本机信息");
        //如果在对象方法中想访问该对象的属性,可以直接写上_属性名称即可
        [IPhone sumWithNumber1:50 andNumber2:50];
        NSLog(@"size=%f,color=%i,model=%i,cpu=%i",_size,_color,_model,_cpu);
    }
    
    -(char *)loadMessage
    {
          return "老婆我们家我做主";
     }
    
    -(int)signal:(int)number
    {
        NSLog(@"打电话给%i",number);
        return 1;
    }
    
    -(int)sendMessage:(int)number :(char *)content
    {
        NSLog(@"发短信给%i,内容是%s",number,content);
        return 0;
    }
    
    -(int)sendMessageWithNumber:(int)number andContent:(char *)content
    {
        NSLog(@"发短信给%i,内容是%s",number,content);
        return 0;
    }
    -(void)callWithNumber:(int)number
    {
        NSLog(@"打电话给%i",number);
    }
    
    +(int)sumWithNumber1:(int)number1 andNumber2:(int)number2
    {
        IPhone *p1=[IPhone new];
        [p1 about];//在类方法中,可以简介调用对象方法,但是在企业开发中,不建议这样子使用
        return number1+number2;
    }
    
    @end
    int main(int argc, const char * argv[]) {
        //1.通过类创建对象
        IPhone *p=[IPhone new];
        p->_color=KIColorBlace;
        p->_model=4;
        p->_cpu=1;
        p->_size=3.5;
        //3.获取对象的属性
        //    NSLog(@"size=%f,color=%i,model=%i,cpu=%i",p->_size,p->_color,p->_model,p->_cpu);
        //4.如果给对象发消息(如果调用对象的方法)
        [p about];
        //注意:OC中的NSLog对C语言中的字符串支持不是很好,如果返回的是中文的C语言字符串可能输出是乱码,也可能什么都不输出
        NSLog(@"%s",[p loadMessage]);
        [IPhone sumWithNumber1:1 andNumber2:2];
        //    [p sendMessage:110 :"help,help"];
        [p sendMessageWithNumber:110 andContent:"help"];
        [p sendMessage:110 :"help,help"];
        [p signal:110];
        [p callWithNumber:119];
        return 0;
    }
    
    void test()
    {
        //1.创建一个对象
        IPhone *p1=[IPhone new];
        //2.利用对象调用假发运算方法
    //    [p1 sumWithNumber1:1 andNumber2:2];
    }
    

    4.OC成员变量、局部变量、全局变量的区别以及内存分析

    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    {
        //写在类声明的大括号中的变量,我们称之为成员变量(或者属性,实例变量)
        //注意:
        //1.成员变量不能离开类,离开类以后就不是成员变量;
        //2.成员变量不能在定义的同时进行初始化;
        //3.成员变量只能通过对象来访问
        //存储在:堆(当前对应的堆的内存空间中)
        //存储在堆中的数据,不会被自动释放,只能程序员手动释放
        int age;
    }
    
    @end
    
    @implementation Person
    
    
    
    @end
    //写在函数和大括号外部的变量,我们称之为全局变量
    //作用域:从定义的那一行开始,一直到文件末尾
    //全局变量可以先定义再初始化,也可以定义的同时初始化
    //存储在:静态区
    //程序一启动就会分配内存空间,知道程序结束才会释放
    int a;
    int b=10;
    int main(int argc, const char * argv[]) {
        //写在函数或者代码块中的变量,我们称之为局部变量
        //作用域:从定义的哪一行开始,一直到遇到大括号或者return
        //局部变量可以先定义再初始化,也可以定义的同时初始化
        //存储在栈中
        //存储在栈中的数据有一个特点,系统会自动给我们释放
        int num=10;
        return 0;
    }
    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject
    {
        //写在类声明的大括号中的变量,我们称之为成员变量(或者属性,实例变量)
        //注意:
        //1.成员变量不能离开类,离开类以后就不是成员变量;
        //2.成员变量不能在定义的同时进行初始化;
        //3.成员变量只能通过对象来访问
        //存储在:堆(当前对应的堆的内存空间中)
        //存储在堆中的数据,不会被自动释放,只能程序员手动释放
        int age;
    }
    
    @end
    
    @implementation Person
    
    
    
    @end
    //写在函数和大括号外部的变量,我们称之为全局变量
    //作用域:从定义的那一行开始,一直到文件末尾
    //全局变量可以先定义再初始化,也可以定义的同时初始化
    //存储在:静态区
    //程序一启动就会分配内存空间,知道程序结束才会释放
    int a;
    int b=10;
    int main(int argc, const char * argv[]) {
        //写在函数或者代码块中的变量,我们称之为局部变量
        //作用域:从定义的哪一行开始,一直到遇到大括号或者return
        //局部变量可以先定义再初始化,也可以定义的同时初始化
        //存储在栈中
        //存储在栈中的数据有一个特点,系统会自动给我们释放
        int num=10;
        return 0;
    }
    

    在以上的代码中已经对成员变量全局变量局部变量做了详细的区分,但是在内存中具体是如何存储的?还没有一个明确的概念。通过阅读一些博客,对物理存储有了一定的了解,分析如下:

    • RAM和ROM区分
      我们所说的内存,其实指的就是RAM,在程序运行期间系统将ROM中的源代码加载到RAM中,我们所说的栈、堆、全局区/静态区、常量区、代码区其实都是位于RAM中。

      RAM:运行内存,不能掉电存储。
      ROM:存储性内存,可以掉电存储,例如内存卡、Flash。
      由于RAM类型不具备掉电存储能力(即一掉电数据消失),所以app程序一般存放于ROM中。RAM的访问速度要远高于ROM,价格也要高。App程序启动,系统会把开启的那个App程序从Flash或ROM里面拷贝到内存(RAM),然后从内存里面执行代码。另一个原因是CPU不能直接从内存卡里面读取指令(需要Flash驱动等等)。

    • RAM中五大分区详解

    1. 栈区(stack):
      存放的局部变量、先进后出、一旦出了作用域就会被销毁;函数跳转地址,现场保护等;
      程序猿不需要管理栈区变量的内存。主要负责函数模块内申请,函数结束时自动释放,存放局部变量,函数变量
      ~栈区地址从高到低分配;
    2. 堆区(heap):
      通过malloc函数或new函数等操作符操作的得到,需要程序员自己管理
      ARC的内存的管理,是编译器再便宜的时候自动添加 retain、release、autorelease;
      ~堆区的地址是从低到高分配
    3. 全局区/静态区(static):
      包括两个部分:未初始化过(.bss段区域) 、初始化过(data段区域),即全局区/静态区在内存中是放在一起的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。
    4. 常量区:存放常量字符串;
    5. 代码区: 存放编写的源代码,由系统自动加载进入;
    6. 注意:也有人将全局区/静态区(static)和常量区联合在一起成为数据区。

    具体图示如下图所示:

    内存分区图解

    5.函数与方法的区别
    首先需要理解的一点是:方法是对象的行为,函数是整个文件的一个功能块。方法只能存在于类内部,函数可以存在于整个文件的任何地方。具体代码如下:

    #import <Foundation/Foundation.h>
    
    
    /*
     *函数和方法的区别
     *1.函数属于整个文件,方法属于某一个类,方法如果离开类就不行
     *2.函数可以直接调用,方法必须用对象或者类来调用
     *注意:虽然函数属于整个文件,但是如果把函数写在类的声明中就会不识别
     *3.不能把函数当做方法调用,也不能将方法当做函数调用
     *
     *
     *方法的注意点:
     *方法可以只有声明没有实现,也可以只有实现没有声明,编译不会报错,但是运行会报错
     *如果方法只有声明而没有实现,则会报错:1.reason:'+[Person demo]:unrecognized selector sent to class 0x100001140',发送了一个不能识别的消息,在Person类中没有+开头的方法
     *reason:'-[Person test]:unrecognized selector sent to instance 0x100001140'
     *类也可以只有实现没有声明,例如:@implementation Person : NSObjec ... @end
     */
    
    @interface Person : NSObject
    //对象方法的声明
    -(void)test;
    //类方法声明
    +(void)demo;
    @end
    
    @implementation Person
    //对象方法的实现
    -(void)test
    {
        NSLog(@"test");
    }
    //类方法的实现
    +(void)demo
    {
        NSLog(@"demo");
    }
    
    @end
    //外部函数的声明
    extern void sum();
    //内部函数的声明
    static void minus();
    //外部函数
    extern void sum()
    {
        printf("sum");
    }
    //内部函数
    static void minus()
    {
        printf("minus");
    }
    
    int main(int argc, const char * argv[]) {
        
        return 0;
    }
    

    6.结构体作为对象属性的细节
    当结构体作为函数(C语言中的称呼)参数或者作为对象属性的情况下,结构体是以拷贝结构体属性的方式进行传递的,此过程传递的不是指针。
    结构体作为属性的代码示例:
    类模型

    #import <Foundation/Foundation.h>
    
    /*
     *合理的设计一个“学生”类
     *学生有*姓名*生日另个属性和说出自己姓名生日的方法
     *要求利用设计的学生类创建学生对象,并且说出自己的姓名和年龄
     */
    
    //定义一个新类型Date
    typedef struct
    {
        int year;
        int month;
        int day;
    }Date;
    
    @interface Student : NSObject
    {
        @public
        NSString *_name;
        
        Date _birthday;
    }
    -(void)say;
    @end
    
    @implementation Student
    
    -(void)say
    {
        NSLog(@"name=%@;year=%i,month=%i,day=%i",_name,_birthday.year,_birthday.month,_birthday.day);
    }
    
    @end
    

    结构体属性修改范例

    int main(int argc, const char * argv[]) {
        //1.创建学生对象
        Student *student=[Student new];
        [student say];
        //2.设置学生对象的属性
        student->_name=@"lxh";
        /*
         *student->_birthday={1992,12,23};错误原因
         *1.结构体只能在定义的时候初始化
         *2.系统并不清楚它({1992,12,23})是数组还是结构体
         */
        student->_birthday=(Date){1992,12,23};
        //student->_birthday=(Date){1992,12,23};这句话的本质其实是:结构体拷贝,将结构体{1992,12,23}强制拷贝给了_birthday
        /*
        Date d1={1999,1,5};
        Date d2;
        d2=d1; //当结构体作为函数的参数的时候,赋值其实是将结构体内部的属性,进行了一次拷贝
               //这里是将d1内部的属性都拷贝了一份赋值给了d2
        d2.year=2000;//修改d2的值并不影响d1的值
        printf("d1=year=%i\n",d1.year);
        printf("d2=year=%i\n",d2.year);
        */
        
        /*
         *这种方式赋值也可以,单个赋值
         */
        student->_birthday.year=1992;
        student->_birthday.month=12;
        student->_birthday.day=23;
        
        /*
         *第三种赋值方式
         */
        Date d={1992,12,23};
        student->_birthday=d;
        return 0;
    }
    

    欢迎关注我的个人微信公众号,免费送计算机各种最新视频资源!你想象不到的精彩!


    0.jpg

    相关文章

      网友评论

          本文标题:重学iOS——1.从C角度看OC

          本文链接:https://www.haomeiwen.com/subject/fpezyxtx.html