基础知识

前言

这里我按照我的思路只是对java和Object-c区别有明显的地方做个总结,如果要学习这门语言建议还是找来一本书看看细节知识点,限于篇幅,这里讲解不全面,大都点到为止。如有错误的地方,欢迎批评指正,一起学习。大伙学过编程都知道,计算机语言的第一门课,都会讲如何敲出“Hello World!”这样一个程序,今天也从这里开始,先看两块代码:

#####Java:

1
2
3
4
5
6
class HelloWorld {
//主函数入口
public static void main(String[] args) {
System.out.println("Hello World!");
}
}

#####Object-c:

1
2
3
4
5
6
7
8
#import <Foundation/Foundation.h>
//主函数入口
int main(int argc, char *argv[]) {
@autoreleasepool {
NSLog(@"Hello World!");
}
return 0;
}

运行上面两段代码都可以在控制台打印出“Hello World!”,从这个Demo里面我们开始引出下面几点内容:

  1. 常见文件扩展名:
后缀扩展名 意义
.java java代码源文件
.class javas源代码编译后的与平台无关的字节码文件
.c C语言代码源文件
.cc、.cpp C++语言代码源文件
.h 头文件
.m Object-c语言代码源文件
.mm Object-c++语言代码源文件
.o、.out C、C++、Object-c语言编译后生产的文件
  1. 程序入口:
    java和objec-c都用main函数作为程序的入口。
  2. 注意返回值
  3. 注意打印语句:当然object-c也可以用C语言中的printf语句来打印。这里的NsLog是Cocoa框架中提供的Foundation框架。
  4. object-c指针变量的概念。
  5. java采用垃圾回收机制,由java虚拟机负责对象内存回收工作。而Object-c没有这样的机制,这里是引入自动释放池(@autoreleasepool),该池会自动回收这些语句创建的对象,释放内存,避免内存泄露。这也是MRC(手动引用计数)中的一种。相对于Object-c2.0引入的ARC(自动引用计数),这个要在内存管理上相对难一点。这也是java程序员学习这块知识的一个难点。
  6. NSLog函数中NS是一个前缀,用来标识这个函数来自Cocoa,而不是其他程序包,而Java用包名来区分这些。
  7. Java字符串只需要用双引号””引起来就可以,而Object-c则需要在此基础上加上@符号,来表示它是一个字符串。
  8. Java编译后会生成相对应的.class文件,而Objec-c编译后会生成对应的.out文件。
  9. Java和object-c注释:单行注释用//... 多行注释用/*...*/

数据类型

  1. 关于两者分隔符、标识符两者并无太大区别,而关键字大体相同,但有个别差别,这里不再
    多说,有兴趣的可以查资料。
  2. Object-c是C语言的超集,所以它支持的类型与C基本相似:这里不再多说,下面先列出基本数据类型:

Objec-c

类型 说明
short int 简称short
int 整型
long int 简称long
long long 更长整型
char 字符型
float 浮动型
double 浮点型
enum 枚举
BOOL 布尔类型:YES对应1,NO对应0
NSInteger long
NSUInteger unsigned long
CGFloat 64位平台相当于double,32位平台相当于float
  1. 在Object-c中定义类型为了平台兼容性,我们建议都用NSInteger、NSUInteger、CGFloat来代替整型和浮点型,很重要一点就是他们都是基本类型。
  2. 包装类:
    • NSValue:通用的包装类,包装short、int、long、float、char、指针、对象id等
    • NSNumber:继承于NSValue,包装short、int、long、float、char、指针
  3. 类型转换:
  • 基本类型之间的转换:
    • 自动类型转型
    • 强制类型转换(用圆括号)
    • 向上转型,也就是类型提升
  • 基本类型与包装类型之间转换:
    • + numberWithXxx:将特定类型值包装为NSNumber;
    • - initWithXxx:先创建一个NSNumber对象,再用基本类型初始化NSNumber;
    • - xxxValue:返回该NSNumber对象包装的基本类型的值;

Java:

类型 说明 包装类
byte 字节类型 Byte
short 短整型 Short
int 整型 Integer
long 长整型 Long
char 字符型 Char
float 浮动型 Float
double 浮点型 Double
boolean 布尔类型:true , false Boolean
  1. 包装类:如上表
  2. 类型转换
  • 基本类型之间的转换:
    • 自动类型转型
    • 强制类型转换(用圆括号)
    • 向上转型,也就是类型提升
  • 基本类型与包装类型之间转换(拆箱与装箱机制):
    • new WrapperClass(primitive):创建对应包装类;
    • WrapperClass.xxxValue():返回对应基本数据类型;

运算符

  • 算术运算符:+(加)、-(减)、* (乘) 、 /(除) 、%(取余)、++(自加)、–(自减)
  • 赋值运算符:=、+=、-=、*=、/=、%=、&=、|=、^=、<<=、>>=、>>>=(仅Java支持)
  • 位运算符:&(按位与)、|(按位或)、~(按位非)、^(按位异或)、<<(左移)、>>(右移)、 >>>(无符号右移,仅Java支持)
  • 比较运算符:>、<、>=、<=、==、!=
  • 逻辑运算符:&&(与)、||(或)、!(非)、^(异或)
  • 三目运算符:(?:)
    这里不过多解释,大伙自行脑补,然后就是注意一下运算符的优先级,是在拿不准就用括号括起来。

流程控制

  • 顺序结构
    • if条件语句
    • switch分支语句
  • 循环结构
    • while循环语句
    • do while循环语句
    • for循环
    • foreach循环(注意Java和Object-c表现不一样,下面遍历数组提到)
    • 控制循环语句:break、continue、return、(goto不再提及,少用)
  • 这里相信大伙都没问题,只强调一点就是代码规范。

数组

Object-c直接使用了C语言的数组,和Java相比定义:

Object-c定义以及初始化:

type arrayName[length] = {ele1,ele2,……eleN};

Java定义及初始化:

  • 静态初始化:type[] arrayName = new type[]{ele1,ele2,……eleN}
    或者type[] arrayName = {ele1,ele2,……eleN}
  • 动态初始化:type[] arrayName = new type[length]
    Java建议使用这种方式定义
    另外还有一种是和上面Object-c一样,但是规范起见不建议使用
  • 用foreach遍历数组

    Java版

    1
    2
    for(type varName:arrayName){
    }

    Object-c版

    1
    2
    for(type varName in arrayName){
    }

多维数组不再多说,自行脑补


C语言基础知识补充

函数

  • C、Java和Object-c的入口函数都是main函数
  • 函数参数传递机制都是值传递,这点大伙可以延伸一下,书本上常引用的例子就是变量之间的值交换。
  • 内部函数:定义时用static修饰,该函数只能被当前源文件中其他的其他函数所调用。
  • 外部函数:定义时用extern修饰,或不使用任何修饰符修饰,可以被任何源文件中的函数调用。
  • 局部变量和全局变量的作用域。
  • 内部全局变量和外部全局变量定义:static修饰,区别就是是否可以被其他源程序文件访问。
  • 动态存储和静态存储

预处理

编译器执行之前,先对预处理命令进行处理,然后再和源程序一起编译。

  • 预处理命令必须以#开头
  • 预处理命令通常位于文件开头部分。
  • 宏定义:#define 宏名称 字符串 ,如这条语句:
    #define PI 3.1415926
    • 宏名称字母大写,它不是变量,也不是常量,编译器处理只需查找和替换为定义的字符串即可。
  • 带参数的宏定义:#define 宏名称(参数列表) 字符串,如这条语句:
    #define AREA(r) PI * r * r;
  • 条件编译第一条指令:#ifdef、#ifndef、#else、#endif,定义如下:

    1
    2
    3
    #ifdef 宏名称
    //执行任意语句
    #endif

    如果定义了指定的宏,则执行#ifdef和#endif之间的语句。

    1
    2
    3
    4
    5
    #ifdef 宏名称
    //任意语句
    #else
    //任意语句
    #endif

    如果定义了指定的宏,则执行#ifdef和#else之间的语句;否则执行#else和#endif之间的语句。

    1
    2
    3
    #ifndef 宏名称
    //任意语句
    #endif

    如果没有定义指定的宏,则执行#ifndef和#endif之间的语句。

    1
    2
    3
    4
    5
    #ifndef 宏名称
    //任意语句
    #else
    //任意语句
    #endif

    如果没有定义指定的宏,则执行#ifndef和#else之间的语句;否则执行#else和#endif之间的语句。

  • 条件编译第二条指令:#if、#elif、#else、#endif

    1
    2
    3
    4
    5
    6
    7
    8
    #if 表达式
    //任意语句
    #elif 表达式
    //任意语句
    ……
    #else
    //任意语句
    #endif

    和分支语句比较像

  • #include#import:两者都可以导入源程序,但前者会有重复导入问题,所以Object-c中我们建议和Java一样使用#import。

##指针
我们可以理解为变量的内存地址。

指针变量

  • 定义:类型* 变量名
  • 运算符:

    1. &:取地址运算符,后面通常跟一个变量,用来取该变量的保存地址。
    2. *:取变量运算符,后面通常跟一个指针变量,用来读取该指针变量所指的变量。
    3. 说清楚两者,看下面一段代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    #import <Foundation/Foundation.h>

    int main(int argc, char *argv[]) {
    @autoreleasepool {
    int a = 10;//定义整型
    int* p;//定义一个指针变量

    p = &a;
    NSLog(@"%d",*p); //取出变量a的值为10
    NSLog(@"%d",a == (*(&a)));//结果为1,也就是相等
    }
    }
  • 指针变量类型先声明后使用,一旦指定了指针变量的类型就只能指向该类型变量:如int* p 语句声明的变量p只能指向int型变量;float* p声明的变量p只能指向float型变量。

数组指针

  • 指针指向了数组中第一个元素的地址。int* p = &arr[0];int* p = arr;
  • 指针运算:
    • 指针赋值
      1. p = &a;将一个已有变量的地址赋给指针变量p;
      2. p = &arr[0];将某个数组元素的内存地址赋给指针变量p;
      3. p = arr;将arr数组的首地址赋给指针变量p;
      4. p = pt;将指针变量pt保存的地址赋值给指针变量p;
      5. 指针变量加/减一个整数n:代表该指针的地址加/减 n*变量大小个字节。
      6. 两指针变量指向同一个数组元素,则两者可以相减,结果返回两个指针所指向数组之间元素的个数。如果不指向同一个数组,则相减没有意义。
      7. 两指针变量指向同一个数组元素,则两者可以比较大小,指向前面的数组元素的指针小于指向后面数组元素的指针。如果不指向同一个数组,则比较大小没有意义。
      • 则有:如arr是数组,那么arr+i代表i+1个元素的地址,因此arr+i和&arr[i]是等价的。
      • 如arr是数组,那么(arr+i)代表(&arr[i]),即*(arr+i)与a[i]是等价的。
      • 则可以用这段代码*(p+n)访问数组arr中第n个元素,其中p指向数组arr的首地址。

指向多维数组的指针变量这里就不再多说。

字符指针

  • 我们知道C语言中没有字符串,所以就引入字符指针定义一个字符串。
    char* str = "Hello World!";
    这里str是一个指向字符型的指针变量,指向该字符数组的第一个元素,也指向该字符数组的首地址。

函数指针

该指针变量指向函数的入口,接下来就可以用该指针来调用函数。

  1. 定义函数指针变量:函数返回值类型 (* 指针变量名)();
  2. 将已有的函数入口(函数名)赋值给函数指针变量。
  3. 使用函数指针变量调用函数:(*函数指针变量)(参数);
  4. 看下面一行代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    #import <Foundation/Foundation.h>

    //求和
    int sum(int a,int b){
    return a+b;
    }
    //差值
    int minus(int a,int b){
    return a-b;
    }

    int main(int argc, char *argv[]) {
    @autoreleasepool {
    int (*fnpt)() = sum;//将求和的函数入口赋给fnpt;
    NSLog(@"和值:%d",(*fnpt)(3,4));//结果为7

    fnpt = minus;
    NSLog(@"差值:%d",(*fnpt)(10,4));//结果为6
    }
    }
  5. 函数指针变量作为函数参数

    • 我们借用上面例子改造一下 ,看下面代码:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      19
      20
      21
      22
      23
      #import <Foundation/Foundation.h>
      //函数指针变量作为参数
      void map(int a,int b ,int (*fnpt)()){
      NSLog(@"结果为:%d",(*fnpt)(a,b));
      }

      //求和
      int sum(int a,int b){
      return a+b;
      }
      //差值
      int minus(int a,int b){
      return a-b;
      }

      int main(int argc, char *argv[]) {
      @autoreleasepool {
      //求和值,结果为7
      map(3,4,sum);
      //求差值,结果为6
      map(10,4,minus);
      }
      }

      不难看出我们通过动态传入函数参数,来达到动态改变调用函数的目的,这类似设计模式中的命令模式。

  6. 返回指针的函数:指针函数,注意与函数指针的区别。一个是变量,一个是函数

    • 我们修改上面的例子:

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      #import <Foundation/Foundation.h>

      int n = 0;
      //求和
      int* sum(int a,int b){
      n = a + b;
      return &n;
      }
      //差值
      int minus(int a,int b){
      return a-b;
      }

      int main(int argc, char *argv[]) {
      @autoreleasepool {
      NSLog(@"结果为:%d",minus(*(sum(3,4)),3));//结果为4
      }
      }

指针数组

注意与数组指针的区别,一个是数组,一个是变量。

  • 定义:类型* 数组变量[长度];

指向指针变量的指针

  • 定义:类型** 变量名;
  • 看下面几行代码:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    #import <Foundation/Foundation.h>

    int main(int argc, char *argv[]) {
    @autoreleasepool {
    int a = 20;
    int* p = &a;
    int** pp = &p;
    NSLog(@"%p",p);//输出指针变量p保存的地址
    NSLog(@"%p",pp);//输出指针变量pp保存的地址
    NSLog(@"%p",*pp);//输出指针变量pp所指变量(也就是指针p)中保存的地址,即和第一个输出值一样
    NSLog(@"%d",**pp);//输出pp所指指针变量指向的变量的地址,即20
    }
    }

结构体

  • 定义结构体:

    1
    2
    3
    4
    struct 结构体类型名
    {
    //成员列表
    }
  • 定义结构体变量:
    struct 结构体名 变量名;

  • 如果上面定义结构体变量繁琐的话,可以使用我们前面讲解的预编译指令起一个更短的别名。
    #define POINT struct point;
  • 使用typedef为已有的结构体类型或者数据类型定义新名称。
    typedef struct POINT POINT;
  • 同时定义结构体类型和结构体变量

    1
    2
    3
    4
    struct 结构体名
    {
    //成员列表
    } 结构体变量1,结构体变量2……;
  • 初始化结构体变量

    • 同时定义结构体类型和结构体变量是不能直接对结构体变量初始化的,只能在定义的时候初始化。
    • 看下面一段代码:
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    #import <Foundation/Foundation.h>

    int main(int argc, char *argv[]) {
    @autoreleasepool {
    struct rect{
    int x;
    int y;
    int width;
    int height;
    } rect1 = {20,30,100,200};//定义的时候直接初始化
    // rect1 = {20,30,100,200};//这种写法是错误的

    typedef struct rect SLRect;
    SLRect rect2 = {20,30,100,200};//使用SLRect定义结构体变量时,是可以直接初始化的
    NSLog(@"rect2的x坐标为:%d,y坐标为:%d,宽为:%d,高为:%d",rect2.x,rect2.y,rect2.width,rect2.height);//rect2的x坐标为:20,y坐标为:30,宽为:100,高为:200
    // rect2 = {10,20,300,400};//这种初始化是错误的
    rect2.x = 25;
    rect2.y = 35;
    rect2.width = 150;
    rect2.height = 250;//依次对结构体变量的成员赋值是正确的
    }
    }
  • 结构体数组不再多说,和之前数组差不多。

  • 定义块:
1
2
3
4
^ [块返回值类型] (形参类型1 形参1,形参类型2 形参2,……)
{
//块执行体
}
  • 定义块以^开头;
  • 定义块的返回值可以省略,而且经常都会省略声明块的返回值类型
  • 定义块无须指定名字;
  • 如果块没有返回值,块无须带参数,通常建议使用void作为占位符。
  • 定义块变量
    块返回值类型 (^块变量) (形参类型1,形参类型2,……);

    • 定义块变量无须声明形参名,只要指定形参类型即可。如果该块不需要形参,建议使用void作为占位符。
    • 看下面一段代码:
    1
    2
    3
    4
    5
    double (^sumResult) (double,double)=
    ^(double num1,double num2)
    {
    return sum1 + sum2;
    }
  • 后面Object-c经常使用到块作为方法参数的情形。

  • 使用typedef定义块变量类型
    typedef 块返回值类型 (^块变量类型) (形参类型1,形参类型2,……);
    typedef double (^sumResult) (double,double);
分享到:
移动开发者/技术爱好者/喜欢开源与分享,你也可以关注微信公众号MobDevGroup,移动开发在线分享:MobDevGroup