iOS面试题收纳-基础

262 阅读13分钟

面向过程(POP)与面向对象(OOP)

  • 面向过程 是一种以过程为中心的编程思想。

    就是分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现,使用的时候一个一个依次调用就可以。注重的是实现过程!

  • 面向对象 是一种以对象为中心的编程思想,有三大特性

    • **封装:**隐藏对象的属性和实现细节,仅对外提供公共访问方式,将变化隔离,便于使用,提高复用性和安全性
    • **继承:**提高代码复用性,建立了类之间的关系,子类可以拥有父类的所有成员变量和公开方法,继承是多态的前提
    • **多态:**父类或接口定义的引用变量可以指向子类或具体实现类的实例对象,提高了程序的拓展性

什么是多态?

  • 多态:在面向对象语言中指同一个接口有多种不同的实现方式。在OC中,多态则是不同对象对同一消息的不同响应方式;子类通过重写父类的方法来改变同一方法的实现就体现了多态性
  • 通俗来讲:多态就父类类型的指针指向子类的对象,在函数(方法)调用的时候可以调用到正确版本的函数(方法)。
  • 多态就是某一类事物的多种形态。继承是多态的前提

编译型和解释型的区别

  • 编译型语言:首先是将源代码编译生成机器指令,再由机器运行机器码 (二进制)
  • 解释型语言:源代码不是直接翻译成机器指令,而是先翻译成中间代码,再由解释器对中间代码进行解释运行

动态语言和静态语言

  • 动态类型语言:是指数据类型的检查是在运行时做的,在运行时可以改变类结构,比如添加移除方法和属性等
  • 静态类型语言:是指数据类型的检查是在运行前(如编译阶段)做的

简要说明const、宏、static、extern区别

const

const:常量修饰符。经常使用的字符串常量,一般是抽成宏,但是苹果不推荐我们抽成宏,推荐我们使用const常量。
- const 作用:限制类型
- 使用const修饰基本变量, 两种写法效果一致, b都是只读变量
  const int b = 5; 
  int const b = 5;   
- 使用const修饰指针变量的变量
  // *p的值不能改变,但p的指向可以修改; 
	const int *p = &a 和 int const *q = &a;
	// p 的指向不能改,但*p 的值可以改
	int * const p = &a;
	// *p 值和 p 的指向都不能改
  const int * const p = &a;
  
  const 在*左边, 指向可变, 值不可变
  const 在*的右边, 指向不可变, 值可变
  const 在*的两边, 都不可变
    
1. const 常量:定义时就初始化,以后不能更改。
2. const 形参:func(const int a){};该形参在函数里不能改变
3. const修饰类成员函数:该函数对成员变量只能进行只读操作
 
作用:
/*
(1)阻止一个变量被改变
(2)声明常量指针和指针常量
 对指针来说,可以指定指针本身为 const,也可以指定指针所指的数据为 const,或二者同时指定为 const;
(3)const修饰形参,表明它是一个输入参数,在函数内部不能改变其值;
(4)对于类的成员函数,若指定其为const类型,则表明其是一个常函数,不能修改类的成员变量;
(5)对于类的成员函数,有时候必须指定其返回值为const类型,以使得其返回值不为”左值”。
*/

/*
* 基本概念:宏是一种批量处理的称谓。
一般说来,宏是一种规则或模式,或称语法替换 ,用于说明某一特定输入(通常是字符串)如何根据预定义的规则转换成对应的输出(通常也是字符串)。
这种替换在预编译时进行,称作宏展开。编译器会在编译前扫描代码,如果遇到我们已经定义好的宏那么就会进行代码替换,宏只会在内存中copy一份,然后全局替换。
宏一般分为对象宏和函数宏。 
宏的弊端:如果代码中大量的使用宏会使预编译时间变长。

static

/*
static 全局变量 表示一个变量在当前文件的全局内可访问
static 函数 表示一个函数只能在当前文件中被访问
static 类成员变量 表示这个成员为全类所共有
static 类成员函数 表示这个函数为全类所共有,而且只能访问静态成员变量
static局部变量 将一个变量声明为函数的局部变量,那么这个局部变量在函数执行完成之后不会被释放,而是继续保留在内存中

作用:
1. 函数体内static变量的作用范围为该函数体,该变量的内存只被分配一次,因此其值在下次调用时仍维持上次的值;
2. 在模块内的static全局变量和函数可以被模块内的函数访问,但不能被模块外其它函数访问;
3. 在类中的static成员变量属于整个类所拥有,对类的所有对象只有一份拷贝;
4. 在类中的static成员函数属于整个类所拥有,这个函数不接收this指针,因而只能访问类的static成员变量。
*/

extern

/*
声明外部全局变量(只能用于声明,不能用于定义)

常用用法(.h结合extern联合使用)
1. 如果在.h文件中声明了extern全局变量,那么在同一个类中的.m文件中对全局变量的赋值必须是:数据类型+变量名(与声明一致)= XXXX结构。
2. 在调用的时候,必须导入.h文件。
	 例如:在viewController.m中调用,则可以引入:ExternModel.h,否则无法识别全局变量。

代码如下:
.h
@interface ExternModel : NSObject
extern NSString *lhString;
@end

.m     
@implementation ExternModel
NSString *lhString = @"hello";
@end
*/

define 和 const常量有什么区别?

  • define在预处理阶段进行替换,const常量在编译阶段使用
  • 宏不做类型检查,仅仅进行替换,const常量有数据类型,会执行类型检查
  • define不能调试,const常量可以调试
  • define定义的常量在替换后运行过程中会不断地占用内存,而const定义的常量存储在数据段只有一份copy,效率更高
  • define可以定义一些简单的函数,const不可以

sizeof关键字

  1. sizeof是在编译阶段处理,且不能被编译为机器码。
  2. sizeof的结果等于对象或类型所占的内存字节数。
  3. sizeof的返回值类型为size_t
  4. 对结构体求sizeof时,有两个原则:
    1. 展开后的结构体的第一个成员的偏移量应当是被展开的结构体中最大的成员的整数倍。
    2. 结构体大小必须是所有成员大小的整数倍,这里所有成员计算的是展开后的成员,而不是将嵌套的结构体当做一个整体。
  • 注意:不能对结构体中的位域成员使用sizeof

了解内联函数么

内联函数是为了减少函数调用的开销,编译器在编译阶段把函数体内的代码复制到函数调用处

内联函数和宏的区别

  1. 宏定义不是函数,但是使用起来像函数。
  2. 预处理器用复制宏代码的方式代替函数的调用,省去了函数压栈退栈过程,提高了效率。

内联函数

  1. 内联函数本质上是一个函数,内联函数一般用于函数体的代码比较简单的函数,不能包含复杂的控制语句,while、switch,并且内联函数本身不能直接调用自身
  2. 如果内联函数的函数体过大,编译器会自动的把这个内联函数变成普通函数。

区别

展开时机
  • 宏定义是在预编译的时候把所有的宏名用宏体来替换,简单的说就是字符串替换
  • 内联函数则是在编译的时候进行代码插入,编译器会在每处调用内联函数的地方直接把内联函数的内容展开,这样可以省去函数的调用的开销,提高效率
类型检查
  • 宏定义是没有类型检查的,无论对还是错都是直接替换
  • 内联函数在编译的时候会进行类型的检查,内联函数满足函数的性质,比如有返回值、参数列表等

内联函数和普通函数的区别

  1. 内联函数和普通函数的参数传递机制相同,但是编译器会在每处调用内联函数的地方将内联函数内容展开,这样既避免了函数调用的开销又没有宏机制的缺陷
  2. 普通函数在被调用的时候,系统首先要到函数的入口地址去执行函数体,执行完成之后再回到函数调用的地方继续执行,函数始终只有一个复制。
  3. 内联函数不需要寻址,当执行到内联函数的时候,将此函数展开,如果程序中有N次调用了内联函数则会有N次展开函数代码
  4. 内联函数有一定的限制,内联函数体要求代码简单,不能包含复杂的结构控制语句。如果内联函数函数体过于复杂,编译器将自动把内联函数当成普通函数来执行

指针常量、常量指针和指向常量的常量指针

/*
指针和 const 谁在前先读谁
*象征着地址,const象征着内容
谁在前面谁就不允许改变
*/

int* const p:
指针常量,其本质是一个常量(用指针修饰)
这个常量的内容是一个指针,不可以修改p指向的地址,但是可以修改*p的值。
定义的时候要赋初值。(数组名也是一个指针常量)

int const *p(const int *p):
常量指针,其本质是一个指针(用const修饰)。
说明它指向的对象是一个常量,这个对象不能被更改,可以修改p指向的地址,但是不可以修改*p的值。

const int *const p:
指向常量的常量指针,既不可以修改p指向的地址,也不可以修改*p的值。

说出以下指针的含义

int** a;
指向一个指针的指针,该指针指向一个整数。

int* a[10];
指向一个有10个指针的数组,每个指针指向一个整数。

int (*a)[10]; // 感觉这么写才更有意义 int[10]* a;
指向一个有10个整数数组的指针。

int (*a)(int);
指向一个函数的指针,该函数有一个整数参数,并返回一个整数。

指针函数和函数指针

  • 指针函数: 顾名思义,它的本质是一个函数,不过它的返回值是一个指针

    // 指针函数
    int* sum(int a, int b) {
        int result = a + b;
        int *c = &result;
        return c;
    }
    
    int *p = sum(10, 20);
    printf("sum:%d\n", *p);
    
  • 函数指针的本质是一个指针,该指针的地址指向了一个函数,所以它是指向函数的指针。

    // 函数指针
    int max(int a, int b){
        return (a > b)?a:b;
    }
    // 指向函数的指针p,指向的函数的签名是 int(int, int)
    int (*p)(int, int) = max;
    int result = p(10, 20);
    printf("result:%d\n", result);
    

关键字volatile有什么含意?并给出不同的例子

  • 一个定义为volatile的变量是说这变量可能会被意想不到地改变,这样,编译器就不会去假设这个变量的值了。精确地说就是,优化器在用到这个变量时必须每次都小心地重新读取这个变量的值,而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子

    • 并行设备的硬件寄存器(如:状态寄存器)
    • 一个中断服务子程序中会访问到的非自动变量(Non-automatic variables);
    • 多线程应用中被几个任务共享的变量
  • 一个参数既可以是 const 还可以是 volatile 吗?解释为什么。

    • 是的。一个例子是只读的状态寄存器。它是 volatile 因为它可能被意想不到地改变。它是 const 因为程序不应该试图去修改它
  • 一个指针可以是 volatile 吗?解释为什么。

    • 是的。尽管这并不很常见。一个例子是当一个中服务子程序修该一个指向一个buffer的指针时

strcpy和memcpy的最大区别是什么?

  • 复制的内容不同。

    strcpy只能复制字符串,而memcpy可以复制任意内容,字符数组、整型、结构体、类等

  • 复制的方法不同。

    strcpy不需要指定长度,它遇到被复制字符串的结束符"\0"才结束,所以容易溢出。memcpy则是根据其第3个参数决定复制的长度

  • 用途不同。通常在复制字符串时用strcpy,而需要复制其他类型数据时则一般用memcpy

SVN 和 Git 区别

  • svn 和 git 都是用来对项目进行版本控制以及代码管理的,可以监测代码及资源的更改变化,有利于实现高效的团队合作
  • svn 是集中式的,集中式是指只有一个远程版本库。git 是分布式的,分布式有本地和远程版本库,本地仓库都保留了整个项目的完整备份
  • svn如果存储远程版本库的服务器挂了,所有人的代码都无法提交,甚至丢失版本库,git则因为有本地版本库而不会有这个问题
  • 由于两者的架构不同,git 和 svn 的分支也是不同的。svn 的分支是一个完整的目录,包含所有的实际文件,和中心仓库是保持同步的,如果某个团队成员创建新的分支,那么会同步到所有的版本成员中,所有人都会收到影响。而 git下创建的分支合并前是不会影响到任何人的,创建分支可以在本地脱机进行任何操作,测试无误后在合并到主分支,然后其他成员才可以看得到

数组和链表的区别

- 数组在内存上给出了连续的空间
- 链表,内存地址上可以是不连续的,每个链表的节点包括原来的内存和下一个节点的信息(单向的一个,双向链表的话,会有两个)

数组: 
- 优点: 使用方便,查询效率比链表高,内存为一连续的区域 
- 缺点: 大小固定,不适合动态存储,不方便动态添加

 链表: 
- 优点: 可动态添加删除,大小可变   
- 缺点: 只能通过顺次指针访问,查询效率低