C和OC的基础语法(易混淆的概念知识)
Posted
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C和OC的基础语法(易混淆的概念知识)相关的知识,希望对你有一定的参考价值。
List
0. 深复制与浅复制, NSObject万能指针、id指针、instancetype区别,单例import、include、@class的区别
strong 与 weak 区别 #define 和 typedef的区别,
static 与 extern 区别,@required与@optional 区 别,@private、@protected 、@public、@package区别
- 变量的命名规则以及规范(4规则,2规范)
- 数据类型转换
- printf与scanf,自增自减,逻辑运算符
- if 语句,三元表达式,switch-case, break 与 continue 区别
- 四种进制,sizeof运算符,位运算,int类型的修饰符,有符号和无符号
- 格式控制符,垃圾值,选择,冒泡,字符串的常用函数
- 指针的存储,指针是什么?指针类型,指针类型,指针运算,const关键字,
- oc 与 c 的区别 , oc新增的数据类型,面向过程与面向对象
- nil和NULL区别,面向对象的三大特征(每个特征的定义),setter、getter,self
- super、私有属性和私有方法、里氏替换原则,description,SEL
- 点语法、@property、@synthesize;构造方法
- 内存管理,内存管理的原则,野指针与僵尸对象,内存管理方法,自动释放池
- ARC 与 MRC,分类,延展。
- block,协议,代理,协议与继承的区别
- 属性的修饰符 retain,release,copy,assign,atomic,nonatomic,strong,weak,readonly区别
- 结构体和类的区别
- 内存五大区域的概念
Content
深复制与浅复制的区别
- 概念
- 浅复制
- 被复制对象的所有变量都含有与原来的对象相同的值,而其所有的对其他对象的引用都仍然指向原来的对象
- 深复制
- 被复制对象会将所有非引用类型的字段复制给新对象,同时将引用类型所指向地址中存的对象复制给新的对象
- 区别
- 区别
- 浅复制就是对内存地址的复制,让目标对象指针和源对象指向同一片内存空间
- 深复制是指复制对象的具体内容,而内存地址是自主分配的,复制结束之后,两个对象虽然存的值是相同的,但是内存地址不一样,两个对象也互不影响,互不干涉
- 浅复制和深复制的区别仅在于对引用类型的对待上,一个是复制的地址,一个是复制的地址指向位置的数据
- 区别补充
- 一个对象中的变量有的是值类型的,有的是引用类型的.对于值类型来说,它的值就是单纯的值,而对于引用类型来说,它的值是地址
- 当一个对象其中的引用字段所指向的地址中的变量变化时,所有浅复制对象中的该引用字段都会发生变化
- 作用及注意点
- ios 中不是所有的对象都支持copy/mutableCopy,遵守NSCopying协议的类可以发送copy消息,遵守NSMutableCopying协议的类才可以发送mutableCopy消息
- 浅复制作用是使得两个对象的成员的值保持一致,在给对象赋值和参数传递的场合使用,有时候一个类中没有指针成员,浅复制就比较合适
- 在给一个对象赋值另一个对象,要实现的功能是在对后一个对象做修改或其他操作对原有对象没有影响,这种情况我们就要做深复制
NSObject 万能指针,id 指针,instancetype 的区别
- 概念:
- NSObject万能指针的概念
- NSObject是OC所有类的父类,依据LSP(里氏替换原则),NSObject指针就可以指向任意的OC对象
- 所以,NSObject指针是1个万能指针
- id指针的概念
- id是1个typedef自定义类型的指针,可以指向任意的OC对象
- 所以,id是1个万能指针
- instancetype关键字的概念
- 如果方法的返回值是instancetype,代表方法的返回值是当前这个类的对象
- 区别:
- NSObject万能指针、id指针的区别
- 概念区别
- id指针可以指向任意OC对象,包括非NSObject对象
- 语法区别
- NSObject万能指针,书写时要加*
- id指针是1个typedef类型的指针,重命名的时候已经有*了,书写是不要加*
- 编译检查区别
- 通过NSObject指针去调用对象方法的时候,编译器会做编译检查
- 通过id类型的指针去调用对象方法的时候,编译器直接通过,不做检查
- 缺点区别
- NSObject缺点:如果要调用指向子类对象的独有的方法,必须强制类型转换
- id缺点:id指针不能使用点语法,会编译错误
- id指针、instancetype的区别
- 概念区别
- instancetype只能作为方法的返回值
- id既可以声明指针变量,作为参数,作为返回值
- 类型区别
- instancetype可以返回和方法所在类相同类型的对象
- id只能返回未知类型的对象
单例【OC】
- 概念
- 一个类的对象,无论在何时创建,无论创建多少次,创建出来的对象都是同一个对象
- 实现方式
- + (instancetype)allocWithZone:(struct _NSZone *)zone
{
static id instance = nil;
if(instance == nil)
{
instance = [super allocWithZone:zone];
}
return instance;
} - 一般为此固定格式
- zone参数没有任何意义 //Apple API Reference ""
- 规范
- 如果为一个类设计了单例模式,Apple规范要求为此类提供一个类方法来快速创建对象
- 格式:defaultxxxxx / sharedxxxxxx
- 作用
- 一些需要全局共享的数据
#import、#include、@class的区别
- 概念:
- #include的概念
- 文件包含指令#include,是一个预处理指令
- 作用:
- 可以将指定的文件内容拷贝到写指令的地方
- 双引号“”,现在当前项目的目录找文件
- 尖括号<>,在编译器的目录中找文件
- #import的概念
- #import是一个预处理指令
- #import作为#include的增强版,可以避免头文件的重复包含
- @class的概念
- 不是预处理指令,它的作用仅仅是有声明这样一个类,可以使用该类型
- 区别:
- 三者的区别
- #include是一个预处理指令,会把文件内容拷贝到写指令的地方,重复调用会重复包含
- #import是一个预处理指令,是#include的增强版,可以避免头文件的重复包含
- @class是一个关键字,而不是预处理指令,作用是声明有这样一个类,但是并不会导入类的头文件
- 扩展
- @class的巧妙用法
- @class不需要拷贝文件,所以性能比#import强
- 可以再头文件中只使用@class声明类,然后在.m中,当具体使用到某个类的时候,再去#import包含头文件,可以提高性能
- 相对路径和绝对路径
- 绝对路径:
- 路径从根目录开始
- 相对路径:相对于当前这个文件夹的路径
- 和当前的文件,删除共同的路径部分,剩余的就是相对路径(相对于main.c的同级路径)
strong 与 weak 的区别
- 概念:
- 强指针和弱指针的概念
- 强指针:
- __strong修饰指针
- 弱指针:
- __weak修饰指针
- 区别:
- 本质上来讲,强指针和弱指针没有任何区别
- 但是在ARC机制下,作为回收对象的基准,如果1个对象没有任何强指针指向他,这个对象就会被立即回收,哪怕这个对象还有弱指针在指向它
#define 和 typedef 的区别
- 概念:
- #define的概念
- #define为宏定义语句,也是预处理指令,在编译之前执行
- 用来定义常量,和宏
- 语法:#define 宏名 宏值
- typedef
- typedef常用来定义一个标识符及关键字的别名,是语言编译过程的一部分,并不实际分配内存空间,可以增强程序和标识符的可读性
- 区别:
- #define、typedef在OC和C中的区别
- 从概念上来看:
- typedef只是为了增加可读性而为标识符另起的新名称
- 而#define在程序中是为了定义常量和宏
- 从执行顺序来看:
- typedef是在编译过程中处理
- 而#define是编译过程前,也就是预处理过程
- 扩展
static 和 extern 的区别【C和OC】
- 概念/区别:
- static 和 extern 的概念
- 总的说来,这两个关键字是用来修饰变量和函数
- static 和 extern修饰局部变量
- static修饰局部变量
- 被static修饰的局部变量叫做静态变量
- 静态变量不再存储在栈区域,而是存储在常量区
- 当函数执行完毕后,这个静态变量不会被回收,再次执行这个函数时,会直接拿这个静态变量使用
- 再次执行时声明静态变量的语句会被略过
- static修饰的静态变量的特点
- 作用域:不变
- 生命周期:延长,直至程序结束
- 声明语句只会执行一次,之后再运行直接略过
- extern不能修饰局部变量
- static和extern修饰全局变量
- 如果定义在模块中的全局变量,使用static修饰,只能在当前模块访问
- 如果定义在模块中的全局变量,使用extern修饰 , 可以跨模块访问
- static和extern修饰函数的效果
- 如果函数被static修饰,那么这个函数只能在当前模块调用
- 如果函数被extern修饰,那么这个函数可以跨模块调用
@required 与 @optional 的区别【OC】
- 概念/区别:
- 无论是@required 与 @optional,遵守协议的类如果不实现的话,编译器都不报错
- @required的概念
- 被@required修饰的方法,如果遵守这个协议的类不去实现的话,编译器就会报警告
- @optional的概念
- 被@optional修饰的方法,如果遵守这个协议的类不去实现的话,编译器不报警告
- 扩展:
- 唯一的作用
- 程序默认是@required
- 在于程序员之间的沟通
- 如果有的方法必须要实现,那么就使用@required
- 如果可以实现也可以不实现,就使用@optional
@pirvate、@protected、@public、@package 的区别【OC】
- 概念/区别:
- 访问修饰符分为以下四类
- @private
- 被@private修饰的属性,叫做私有属性,除了这个类的内部,其他地方都是无权访问的
- 父类的私有成员,子类可以继承,但是依旧无法访问继承来的私有成员
- @protected
- 受保护的,被@protected修饰的属性只能在本类和本类的子类中访问
- @package
- 被@package修饰的属性,只能在当前这个target中访问
- @public
- 公共属性,在任意地方都可以访问这些属性
- 扩展:
- 没有为属性添加访问修饰符,默认为@protected
- 访问修饰符的作用域
- 从访问修饰符开始以下,知道遇到另外一个访问修饰符,或大括号,作用结束
- 使用建议
- 对属性进行封装,不使用 @public,用 @property 或 setter、getter 方法
- 如果父类有属性不希望子类访问,用 @private
- 如果父类有属性想给子类访问,但是别的类别想,用 @protected
- 平时编程用默认的 @protected 就足够了
- 权限大小
- @public > @protected > @private
变量的命名规则以及规范【C和OC】
- 概念:
- C语言变量的命名规则以及规范
- 命名规则
- 变量名只能以任意的字母、下划线、$开头,不能以数字开头
- 不能与C语言的关键字重名
- C语言严格区分大小写
- 变量一定要先声明再使用
- 同一个大括号中,不允许定义多个变量名相同的变量(不允许重复定义)
- 命名规范
- 变量的名字要取的有意义,知名达意
- 驼峰式命名,第1个单词的首字母小写,其他单词的首字母大写
- 扩展:
- OC语言中的命名规范更为重要,比如命名类通常以大写字母开头,知名达意,找到能反映变量或对象使用意图的名称尤为重要。富有意义的名称可以显著增强程序的可读性,并可以在调试和文档编写阶段受益匪浅。
- 例如:
- AddressBook —— 可能是个类名
- currentEntry —— 可能是一个对象
- addNewEntry —— 可能是一个方法名
数据类型转换
- 概念:
- 隐式转换:
- 又名自动类型转换
- 当我们为变量赋值的时候,当赋值符号“=”左右两边的数据类型不一致,这个时候C语言会将右侧赋值的类型,转换成左侧变量的类型,进行自动转换
- 显式转换:
- 又名强制类型转换
- 当我们必须要将某一类型的数据强行转换成另外一种类型,则可以利用强制类型转换运算符(说白了就是俩括号)进行转换,这种强制类型转换称为显式转换
- 举例说明数据类型转换
- 显示转换:
- 例1:int num = 3.94;
- 当我们给一个int类型的变量赋值一个小数,C语言就会直接忽略小数部分,数据失真
- 例2:double d1 = 3.14f;
- 当我们给一个double类型的变量赋值一个float类型的小数,精度过高,浪费空间
- 隐式转换:
- 例3:(int)4.2;
- 这个结果是4,强制类型转换的目的是使表达式的值得数据类型发生改变,从而使不同类型数据之间的运算能够进行下去
- 扩展
- 类型转换的弊端
- 由于类型转换将占用系统时间,过多的转换会降低程序的运行效率,在设计程序时应尽量选择好数据类型,以减少不必要的数据转换。
printf 和 scanf
- 概念:
- printf的概念:
- printf的作用:向控制台输出信息
- scanf的概念:
- 接收用户输入的数据,赋值给指定的变量
- 语法格式:
- scanf(“格式控制符”,变量地址列表);
- scanf函数的执行原理:
- scanf是1个阻塞式函数
- 当CPU执行scanf函数的时候,程序就会暂停执行,并等待用户输入数据
- 待用户输入完毕按下回车后,就会将输入的数据赋值给指定变量
- 然后程序往下执
- int 整型
- %d:读取 int 类型的数据,十进制
- %o:读取 int 类型的数据,八进制
- %x:读取 int 类型的数据,十六进制
- ※注:没有二进制输出格式符
- %hd:读取 short int 类型的数据
- %ld:读取 long int 类型的数据
- %lld:读取 long long int 类型的数据
- %u:读取 unsigned int 类型的数据
- %hu:读取 unsigned short 类型的数据
- %lu:读取 unsigned long 类型的数据
- %llu:读取 unsigned long long 类型的数据
- float,double 浮点数类型
- %f:读取 float 类型的数据
- %lf:读取 double 类型的数据
- char 字符类型
- %c:读取 char 类型的数据
- %p
- %p表示输出的格式转换为指针,即输出变量的地址
- 扩展:
- rewind(stdin)的使用需要
- 取地址符的遗落
自增自减
- 概念:
- 自增自减的概念:
- 对变量操作结果增加1或减少1
- 自增自减表达式作为一个表达式,可以用1个变量将表达式的结果存储起来
- 前自增或后自增
- 前自增表达式
- ++num
- 先将自身的值+1,再将自身的值取出来作为表达式的结果
- 后自增表达式
- num++
- 将自身的值取出来作为后自增表达式的结果,然后再将自身的值+1
- 扩展:
- 常作为循环控制条件使用
逻辑运算符
- 概念:
- 概述三种逻辑运算符
- 逻辑与:&&
- 只有当等式两边的条件都成立,整个逻辑表达式才成立
- 逻辑或:||
- 只要有一边的条件成立,整个逻辑表达式就成立
- 只有两边都不成立,整个逻辑表达式才不成立1
- 左边条件成立,右边就不执行了
- 断路问题:
- 逻辑表达式在执行的时候,是先计算左边条件的结果,再计算右边的、如果左边的条件不成立,右边的条件根本不会去执行
- 先考虑断路问题,再考虑优先级
- 逻辑非:!
- 将真变成假,假变成真
- 运算级别高,先取反,再比较
//很简单
//e.g. !num,只要num不为0,!num即为假(0)
//只要num为0 , !num即为真(1) , !表达式
//道理相同
- 概述逻辑运算符的结合性
- &&和||是双目运算符,具有左结合性,也就是需要有两个变量或表达式参与运算
- !为单目运算符,具有右结合性
- 概述逻辑运算符的优先级
- && 和 || 优先级比关系运算符 (>,<,>=,<=,!=,==) 低
- ! 的优先级要比算数运算符 (+,-,*,/,%) 高,更是高于 && 和 ||
- 扩展:
- 常作为判断语句的判断条件使用
if 语句,三元表达式,switch-case的区别
- 概念:
- 概述 if 语句的概念
- 实现1段代码只有在满足指定条件的时候执行,否则不执行
- 执行步骤:
- 先判断if后面的条件表达式的真假
- 如果为真,条件成立就会执行if块中的代码,执行完毕之后再继续往下执行
- 如果为假,条件不成立,会略过if块中的代码继续往下执行
- 概述三元表达式的概念
- 三元表达式的结果:如果条件表达式成立,那么这个三元表达式的结果就是值1,否则就是值2
- 语法:
- 条件表达式 ?值1 : 值2
- 三元表达式可以部分代替 if - else 语句的判断
- 条件表达式的位置,也可以写上一个普通的表达式
- 值1,值2也可以是表达式的形式
- 概述switch-case的概念
- switch是多分支选择语句,处理多种同类型选择,提高可读性
- case 穿透
- 如果case中没有break,那么switch就不会结束
- 会执行下一个case里面的语句,而不进行判断,这个现象就是case穿透现象
- 阐述三者区别和扩展
- 三者的用法不同
- if:通常用来判断某一个范围,条件成立就会执行if块中的代码
- switch:通常用来判断某一些固定值,当满足某一个case的值,就执行这个case里面的语句
- 三元表达式:三元表达式可以部分代替 if - else 语句的判断
- 扩展:
- 三元表达式用于三数排序比大小
- 三元表达式用于枚举性别
break 与 continue 区别
- 概念:
- 概述 break 的概念
- 跳出循环,然后执行循环体以外的内容,另外break也可用在switch-case结构中,作用为跳出switch结构,break只能用在循环结构和switch结构中
- 概述continue的概念
- 加速循环,也就是会忽略continue以下的语句,直接进行下一次循环条件的判定,continue语句只能用在循环语句中
- 区别:
- 作用区别
- continue只是结束本次循环,并不会终止循环
- break则是结束整个循环过程,而不再进行循环条件的判定
- 用途区别
- break可以用在switch-case结构中用来跳出switch-case结构
- continue语句只能用在循环语句中
- 扩展:
- return
- return语句在C中使用
- 跳出当前函数,回到函数的调用处继续执行,并返回一个与函数返回值类型相同的值,若返回值为void类型,则不需要返回值,直接return即可
- return语句在OC中使用
- 跳出当前方法,回到方法的调用处继续执行,并返回一个与方法返回值类型相同的值,这点,和C是相同的
- 不同之处在于,OC中很少很少用到函数,绝大多数为方法
垃圾值
- 概念:
- 概述垃圾值的概念
- 程序作用域执行完毕之后,定义在里面的变量就会被系统立即回收
- 所占用的字节空间被释放,但占用字节的数据不会清空
- 当新变量被分配到这些字节空间,出现的垃圾值就是原来遗留下的值
选择排序和冒泡排序
- 答题顺序:
- 概述选择排序和冒泡排序
- 概念:
- 概述选择排序和冒泡排序
- 选择排序
- 每一趟从待排序的记录中选出关键字最小的记录,顺序放在已排好序的子文件的最后,直到全部记录排序完毕。
- 冒泡排序
- 比较相邻的元素。如果第一个比第二个大,就交换他们两个
- 对每一对相邻元素作同样的工作,从开始第一对到结尾的最后一对。在这一点,最后的元素应该会是最大的数
- 针对所有的元素重复以上的步骤,除了最后一个
- 持续每次对越来越少的元素重复上面的步骤,直到没有任何一对数字需要比较
字符串的常用函数
- 答题顺序:
- C语言中字符串的常用函数
- 概念:
- C语言中字符串的常用函数
- puts( )函数
- 用于输出字符串
- 语法格式:puts(字符数组名)
- 优点:自动换行
- 缺点:只能输出字符串
- gets( )函数
- 从控制台接收用户输入的1个字符串数据
- 语法格式:gets(字符串)
- 优点:当用户输入的字符串包含空格的时候,会连空格一起接收
- 缺点:当字符数组长度不够,字符串过长,使用这个函数就会造成崩溃
- 以下四个函数需要声明string.h头文件
- strlen( )函数
- 获取字符串长度,但是不包括‘ \0 ’
- 语法格式:unsigned long len = stolen(字符串1)
- strcmp( )函数
- 用来比较两个字符串大小
- strcmp(字符串1,字符串2)
- 正数1大于2,负数1小于2,零相等,比较的是相同位的字符的ASCII码值的大小
- strcpy( )函数
- 把存储在1个字符数组中的字符串数据拷贝到另外1个字符数组中存储
- strcpy(字符串1,字符串2)//将 字符串2 拷贝到 字符串1 中
- 可能的问题:
- 字符串1的数组长度不够,无法存储字符串2,运行就会崩溃
- 字符串1数组长度过长,以最近的\0为标准结束
- strcat( )函数
- 将字符串2连接在字符串1后面
- strcat(字符串1,字符串2)
- 可能的问题:
- 字符串1的数组长度不够,无法存储字符串2,运行就会崩溃
指针【C和OC】
- 概念:
- 指针概述
- 什么是指针,指针的作用
- 变量的地址,就叫做指针
- 指针变量就是专门用来存储另一个变量地址的变量
- 存储了谁的地址,这个指针变量就指向了谁
- 直接访问和间接访问
- 直接访问:直接赋值
- 间接访问:可以通过指针变量找到这个指针指向的变量,故,通过指针变量就可以间接访问/修改/获取这个指向的变量的数据
- 野指针
- 乱指的指针,指针变量中存储的值,是一个垃圾值
- 定义一个指针,但是没有给他赋初始值,那么这个指针就是野指针
- 指针类型的内涵
- 不管什么类型的指针,都是8个字节
- 指针存储的是所指向变量的最低位字节的地址
- 通过指针操作变量时,指针变量的类型,决定了要操作多少连续字节的空间
- 多级指针
- 指针运算
- 指针与整数之间的加减法
- 并不是在指针地址的基础上加1个字节,而是在这个指针地址的基础上加1个单位变量占用的字节数
- 指针和指针之间的减法运算
- 绝大多数情况,用于判断数组的两个元素之间相差多少个元素
- 两个语言涉及的扩展
- 指针的范用性概述
- 数组指针和指针数组
- 两者区别
- 数组指针
- 首先它是一个指针,存储了数组地址的指针,存储了数组第0个元素地址的指针
- 指针数组
- 首先它是一个数组,数组元素全是指针变量
- 数组的四种遍历方式
- 指向函数的指针和指针作为函数的参数
- OC语言万能指针概述
- 见NSObject、id万能指针
const关键字
- 概念:
- const关键字的概念
- const修饰基本数据类型和数组
- const是1个关键字,是来修饰我们的变量的。
- 也就是说在声明变量的同时,可以使用const关键字来修饰
- const int num = 10;
- 一般情况下来说被const修饰的变量具备一定程度上的不可变形
- 所以说被const修饰的变量我们叫做只读变量
- const修饰本数据类型的变量
- 基本数据类型:int ,double, gloat , char
- 这个时候,num变量的值只能去取值,而不能去修改
- 位置不同,意义一样
- const修饰数组
- 数组的元素值不能修改
- 位置不同,意义一样
- const修饰指针
- 无法通过p1指针去修改指针指向的变量的值,但可以直接对变量进行赋值
- 位置不同,意义一样
- 不能修改p1指针的地址,但是可以通过p1去修改p1指向的变量的值//有问题
- 无法通过p1指针去修改指针指向的变量的值,并且直接对变量复制也不行
- const关键字的使用场景
- const的特点:被const修饰的变量,是只读变量,只能取值,而不能改值
- 所以,const变量的值,自始至终都不会放生变化
- 当某些数据是固定的,在整个程序运行期间都不会发生变化并且你也不允许别人去修改,那么这个时候我们就可以使用const。
- 当函数的参数是1个指针的时候,这个时候,函数的内部是有可能会修改实参变量的值,那么这个时候,就可以给参数加一个const
OC语言 与 C语言 的区别
- 概念:
- 文件扩展名的区别
- C语言源文件的扩展名是.c,而OC的扩展名是.m
- 两者编程思想的区别
- C语言
- C语言是面向过程的语言,通过分析出解决问题所需要的步骤,然后用函数逐步实现,使用时逐步调用。
- C语言层次清晰,处理能力强,可移植性强,可以直接访问内存的物理地址进行操作,也可以对硬件编程操作
- OC语言
- OC语言是面向对象的语言,通过面向对象的方式将现实事物抽象成对象,将关系抽象成类和继承,根据抽象关键问题域来解决问题,更利于用人的理解方式解决问题
- 这里可以举例说明:组装电脑...
- 面向对象有三大特征,封装,继承,多态
- OC语言从C语言衍化而来,完全兼容C语言
- 两者的拓展和练习
- OC新增了部分内容包括
- BOOL数据类型
- 类
- id类型指针
- Block类型
- SEL和nil类型
- 增强的for循环forin
- 异常处理代码@[email protected]@finally
- 语法区别
- #import默认有条件编译的代码,即防止重复引入头文件
- NSLog输出的是对象,默认有换行,并且有时间戳和项目名
- printf输出的是char *字符串
- OC中类方法和对象方法可以重名,C中函数不可以
nil和NULL区别
- 答题顺序:
- 概述nil和NULL的概念
- 阐述nil和NULL的区别
- 概念:
- 概述nil和NULL的概念
- nil的概念
- 初始化赋值,空值,和NULL完全等价,本质上都是0
- 如果对象的指针指向nil,访问对象的成员变量,会报错,但是调用对象的方法,不会报错,但是没有效果.
- NULL的概念
- 初始化赋值,空值
- 区别:
- 阐述nil和NULL的区别
- nil在苹果官方API Reference中的解释是“Defines the id of a null instance.”
- 意思就是定义一个对象为空,作为指针针对对象使用
- 所以如果是OC对象的指针初始化值为nil,而其他基本数据类型的指针初始化为NULL
- nil指针的是一个空对象,NULL代表指向其他类型(如:基本类型、C类型)的空指针
面向对象的三大特征
- 概念:
- 概述面向对象的三大特征
- 封装的概念
- 封装,就是把客观事物封装成抽象的类,并且类可以吧自己的数据和方法只让可信的类或者对象操作,对不可信的进行内部代码屏蔽
- 函数的封装
- 把执行某个功能的代码封装起来,屏蔽代码内部的实现,使用方便
- 类的封装
- 把类的特征以及类的行为进行封装不需要知道类的内部复杂实现
- 继承的概念
- 继承,通过继承的特性,子类可以拥有父类中的所有成员变量和方法,而不需要自己创建
- 通过继承创建的新类称为“子类”或“派生类”
- 被继承的类称为“基类”,“父类”,“超类”
- 多态的概念
- 多态,就是指同一种方法,不同的表现形态
- 使用父类的指针指向子类对象,然后通过父类指针调用父类的某个方法,如果在子类中对该方法进行了重写,那么实际调用的是子类中的实现
- 降低代码之间的耦合性,增强代码之间的扩展性
setter 和 getter 方法
- 概念:
- 概述两种方法的概念
- setter方法
- 封装数据方法
- 外界如果想给成员变量赋值,必须调用对应的setter方法,在setter内部,进行逻辑判断
- setter命名规范:set开头,后面加上成员变量名称,首字母必须要大写
- getter方法
- 获取封装数据方法
- 外界需要在方法中访问当前类的成员变量,调用getter方法访问成员量的值
- getter命名规范:直接使用成员变量的名字去掉下划线
- 属性的封装规范
- 只要一个类的成员变量需要被外界访问,就需要根据读写的要求,为该成员变量封装一个set方法和一个get方法
self 和 super
- 概念:
- self 和 super 关键字的概念和区别
- self 的实质是1个指针
- self 可以用在对象方法和类方法中
- self 在对象方法中,self指向当前对象,可以调用其他对象方法
- self 在类方法中,self指向当前类,可以调用其他类方法,不可以调用对象方法
- super 指代的是父类对象
- super 可以用在对象方法和类方法中
- 在子类的对象方法中调用从父类继承的对象方法
- 使用super显示调用从父类继承过来的类方法
- super关键字不能访问属性,还得用self
私有属性 和 私有方法
- 概念:
- 私有属性和私有方法的概念和区别
- 私有属性
- 被@private修饰的属性,叫做私有属性,但是私有的不够彻底,Xcode依旧会提示但是无权访问
- 真私有属性
- 类的属性还可以定义在 @implementation中,即为真私有属性
- 私有属性和真私有属性的唯一区别
- Xcode无提示
- 私有方法
- 方法只写实现,不写声明,那么这个方法就是私有方法
- 只在.m文件中实现,而不在.h文件中声明
- OC中没有真正的私有方法
里氏替换原则(LSP)
- 概念:
- 里氏替换原则的概念
- 里氏替换原则的本质
- 父类的指针可以指向子类的对象
- 里氏替换原则的表现形式
- 当1个父类指针,指向1个子类对象的时候,就是里氏替换原则
- 里氏替换原则的有点
- 1个指针不仅仅可以存储本类对象的地址,还可以存储子类对象的地址
- 如果1个指针的类型是NSObject类型,那么这个指针就可以储存任意的OC对象地址
- 所以,NSObject类型的指针,我们叫做万能指针
- 如果1个数组的元素的类型是NSObject*类型的,那么久意味着这个数组中可以存储任意的OC对象
- 如果你发现1个方法的参数是1个父类对象,那么实参可以是1个父类对象,也可以是1个子类对象
- 里氏替换原则的局限性
- 当1个父类指针指向1个子类对象的时候
- 只能通过这个父类指针去访问这个子类对象中的父类成员,子类独有的无法访问
description 方法
- 概念:
- 概述description方法的概念
- description方法,是定义在NSObject类中的1个方法
- 使用NSLog和@%输出某个对象时,会调用对象的description方法,并拿到返回值进行输出。
- 返回如下格式的字符串
- <类名:对象的内存地址> //<HMPerson:p1对象的地址>
- %@的原理
- NSLog函数使用%@打印1个对象的时候,内部是如何做的?
- 先调用传入的对象的descirption方法,返回值是NSString字符串
- 拿到这个方法返回的字符串,将这个字符串输出到控制台
- description方法的重写
- 一般我们在重写对象description方法时直接返回NSString调用stringWithFormat:这个静态方法的结果即可,它返回的是一个NSString字符串类型 //这里第一句话有点儿拗口,不过意思是对的
- 并且,如果是在MRC模式下,这里stringWithFormat:方法创建的字符串对象是不需要释放的,因为stringWithFormat:方法为静态方法,一般情况下静态方法返回的对象都不需要手动释放
SEL数据类型
- 概念:
- 概述SEL数据类型的概念
- 类里面的方法都是被转换成SEL变量进行存储的。
- 如何定义SEL变量存储对象方法
- SEL sayHiSel;//定义一个变量来存储方法 sayHi
sayHiSel = @selector(sayHi);////@selector(方法名)根据一个方法名,返回一个SEL对象
HMPerson *zhangsan = [HMPerson new];
[zhangsan performSelector:sayHiSel];
点语法
- 概念:
- 概述点语法的概念
- 点语法的本质是方法的调用,转换成相应的 set 和 get 方法
- 点语法的使用
- 如果点语法出现在赋值号左边,相当于赋值,那么这个语法就是调用了对象的 setter方法
- p.name = @"小8";
- 如果点语法出现在等号右边,相当于取值,这个点语法就是调用了对象的getter方法
- NSString *name = p.name;
- 当点语法单独使用的时候,一般是取值
- NSLog(@"xxx = %@",p.name);
@property 和 @synthesize【OC】
- 概念:
- xcode 4.2版本前的@property使用概述
- 在xcode4.2之前,@property和@synthesize自动生成属性,声明并实现setter和getter方法
- 单独使用@property的时候,他只会自动生成setter和getter方法的声明
- 注意:[xcode 4.2]之前
- @property可以自动生成属性,但是属性不带下划线
- 默认@synthesize实现中,也是给自己生成的那个不带下划线的属性赋值
- 当前版本(xcode 4.2版本后)的@property使用概述
- @property自动生成setter和getter方法的声明和实现
- 自动生成的属性自带下划线,并且是真私有属性
- 类型相同的前提下,可以批量定义
- @property int num1,num2,num3;
- @property生成的方法实现是没有任何逻辑验证的,若果需要逻辑验证,可以在.m文件重写这个方法
- 如果setter重写,会自动生成私有属性和getter
- 如果getter重写,会自动生成私有属性和setter
- 如果setter和getter同时重写,@property不会自动生成私有属性了
- @property的参数
- 和多线程相关的参数
- atomic:默认值,生成的setter和getter方法安全但是性能低下
- nonatomic:生成的setter和getter方法不安全但是性能高,常用
- 与生成的setter方法实现相关的参数
- assign:默认值,生成setter方法的实现就是直接赋值,用于非OC对象
- retain:生成的setter方法,是标准的MRC内存管理代码,用于OC对象
- 是否生成只读,读写的封装参数
- readwrite:默认值,同时生成setter和和getter方法
- readonly:只生成getter方法
- 指定生成的getter setter方法的名称参数
- setter:无论什么情况下别用这个方法改setter的名字
- getter:一般情况下getter的名字也不改,只有属性是BOOL类型的,我们才改getter的名字,以is开头,提高代码的阅读性
构造方法【OC】
- 概念:
- 概述init方法(构造方法)的概念
- init方法,称为构造方法
- init方法做的事情:为对象的属性赋默认值,初始化对象,和其他未知工作
- 重写init方法的规范
- 若希望对象创建出来之后,对象的属性默认值不是0 nil NULL,就重写init方法
- 先调用父类的init方法
- init方法有返回值,返回的是当前对象
- 判断父类的init方法有没有执行成功,如果成功,自定义初始化属性
- 返回当前对象,self
- -(instancetype)init
{
self = [super init];
if (self != nil) //说明父类的init方法执行成功
{
self.name = @"小明";
self.age = 18;
}
return self;
}
内存五大区域的概念
- 五大区域
- 栈
- 堆
- BSS段
- 常量区/全局区/静态区
- 代码段
- 栈
- stack
- 存放程序局部变量,一般是"{}"中定义的的变量
- 堆
- heap
- 在C中为,程序员手动申请(如malloc函数等)分配的空间,可动态扩张和减少,在OC中,堆区存储OC对象
- BSS段
- BSS segment
- 存储程序未初始化的全局变量和静态变量
- 常量区/全局区/静态区/数据段/data段
- data segment
- 存储已经初始化的全局变量,静态变量和常量的区域
- 代码段
- code segment/text segment
- 存放程序执行代码,有些字符串常量也保存在此区域
内存管理
- 概念:
- 概述内存管理的概念
- 为何只关注堆区的内存管理?
- 内存中有五大区域,只有堆区的OC对象不会自动释放内存。
- 对象存储在堆区并占用很大一部分空间。由于堆区的对象不会自动释放,需要手动管理,如不进行管理就要等到程序结束才会释放。
- 这样会造成内存泄漏,系统变慢,效能低下。
- 内存泄漏
- 指的是对象没有在该回收的时候回收,而是一直驻留在内存中,知道程序结束的时候才被释放
- 内存管理的原则
- 只管理堆区的OC对象的释放
- 只有当这个OC对象无人使用的时候,才可以被释放
- 有对象的创建,就必须要匹配1个release
- 谁负责retain,谁就要负责release
- 谁说要使用这个对象,那么就要在使用的时候retain,不用了的时候release
- retain的次数要和release的次数相匹配
- 只有在多1个人使用这个对象的时候,才retain
- 只有在少1个人使用这个对象的时候,才release
- 有始有终,成对出现,出入平衡
- 引用计数器的概念
- 如何判断OC对象是否有人使用呢?
- 每个OC对象都有一个引用计数器(retainCount),表示对象的使用个数
- 新建对象,retainCount值默认是1
- 每多一人要使用对象的时候,reatinCount+1
- 每少一人要使用对象的时候,reatinCount-1
- 当retainCount = 0的时候,系统会立即回收该对象,同时给对象发送dealloc消息
- 程序员如何操作引用计数器
- 为对象发送1条retain消息,那么这个对象的引用计数器的值就会+1
- 为对象发送1条release消息,这个对象的引用计数器的值就会-1
- 为对象发送1条retainCount消息,就可以得到这个对象的引用计数器的值
- 内存管理的分类
- MRC:Manual Reference Counting 手动引用计数
- 需要程序员手动的写代码,来改变对象的引用计数器的值
- ARC:Automatic Reference Counting 自动引用计数
- ARC是在iOS5之后,2011年之后
- 不需要程序员手动改变对象引用计数器的值,系统自动来改变引用计数器的值
野指针在C和OC中的区别
- 概念:
- C语言中野指针的概念
- 乱指的指针,指针变量中存储的值,是一个垃圾值
- 定义一个指针,但是没有给他赋初始值,那么这个指针就是野指针
- OC语言中野指针的概念
- 1个指针指向的对象已经被释放了,那么这个指针就叫做野指针
- 扩展:
- 野指针访问僵尸对象的问题
- 当我们通过野指针去访问僵尸对象的时候,会报错
- 为了避免报错,当1个指针成为野指针以后,为这个指针赋值为nil
僵尸对象
- 概念:
- 僵尸对象的概念:
- 一个对象已经被回收,但是所在的内存空间还没有分配给别人,这样的对象就叫做僵尸对象
- 僵尸对象有可能可以访问,也有可能不能访问
- 当僵尸对象所占用的空间还没有分配给别人使用的时候,这个对象的数据其实仍然存在,通过指针仍然可以找到这个对象,所以这个时候这个对象还可以使用
- 当将是对象所占用的空间已经分配给别人的时候,这个对象就不存在了无法使用
- 我们认为,一旦一个对象成为了僵尸对象后,这个对象无论如何都不应该被使用了
- 僵尸对象的实时检测:
- 当我们开启僵尸对象监测的时候,通过1个指针去访问1个对象的时候,只要这个对象是僵尸对象,就会报错
- xcode不默认开启僵尸对象监测是因为这样会浪费性能
- 僵尸对象无法复活
内存管理方法
- OC中没有垃圾回收机制,OC中内存管理依赖对象引用计数器(Reference Counting)来进行,通过系统来销毁对象
- 引用计数器(Reference Counting)概念
- OC中每个对象都有一个与之对应的引用计数器,当一个对象在创建之后它的引用计数器值加1,当调用这个对象的alloc,retain,new,copy方法之后,引用计数器在原来值的基础上自动加1
- 当调用这个对象的release方法之后,它的引用计数器的值在原来的基础上自动减1
- 如果一个对象的引用计数器的值为0,系统会自动调用这个对象的dealloc方法来销毁这个对象
- 自动引用计数(ARC)和手动引用计数(MRC)
- 顾名思义,自动引用计数是系统自动对对象的引用计数器进行加减操作
- 手动引用计数需要程序员手动的进行计数器值的加减操作
- MRC和ARC即是OC内存管理的两种方法
自动释放池
- 概念:
- 自动释放池的概念
- 存储在自动释放池中的对象,在自动释放池被销毁的时候,会自动的调用这个对象的release方法
- 自动释放池的好处:将创建对象存储到自动释放池,可以省略创建对象以后要匹配的release代码
- 大括号代表自动释放池的范围
- 只要在自动释放池的范围内,调用对象的autorelease方法,就可以放到自动释放池中了
- @autoreleasepool {
Student *p1 = [[[Student alloc] init] autorelease];
} - 使用自动释放池的七大注意
- 只有在自动释放池中,调用autorelease方法才能存储到这个自动释放池中,只是将对象代码写在自动释放池中,是没用的
- 对象的创建可以在自动释放池外,但是调动autorelease要在自动释放池作用域中
- 当自动释放池结束的时候,仅仅是对存储在自动释放池的对象发送一条release消息,而不是销毁对象
- 如果在自动释放池中,调用同1个对象的autorelease方法多次,就会向对象发送多次release消息,那么这个时候就会出现将是对象错误
- 如果自动释放池,调用了存储在自动释放池中的对象的release方法,在自动释放池结束的时候,还会再调用对象的release方法,这个时候就有可能造成野指针(僵尸对象)
- 将对想存储到自动释放池,并不会使对象的引用计数器+1
- 自动释放池可以嵌套
ARC和MRC
- 概念:
- MRC的概念
- Manual Reference Counting 手动引用计数
- 需要程序员手动的写代码,来改变对象的引用计数器的值
- ARC的概念
- :Automatic Reference Counting 自动引用计数
- Xcode在4.2版本中引入ARC
- //确认为4.2而非4.4 来自API Reference
- //ARC是在iOS5之后,2011年之后 //此点以Xcode版本来描述更合适
- 不需要程序员手动改变对象引用计数器的值,系统来自动改变
- 拓展
- ARC的使用规则 //Apple API Reference翻译而来 , 9条 , 酌情记...
- 不能调用dealloc,不能重写和调用retain,release,retainCount 和autorelease,同理,@selector(retain),@selector(release)这些曲线救国的方法也不能调用。 dealloc虽然能够重写,但是不能调用[super dealloc]之类的方法,CoreFoundation框架由于非从属cocoa框架,所以CFRetain和CFRelease仍然正常使用
- 不能使用NSAllocateObjec或NSDeallocateObject函数来创建对象
- 不能在C语言的结构体中使用对象指针,同时建议用Objective-C的类来管理数据而不是结构体
- 在id和void *类型之间不再进行频繁转换,如果你看不懂这点,就略过吧
- 不得使用NSAutoreleasePool对象。ARC中,全部使用@autorelease关键字代替,且比NSAutoreleasePool更高效
- 不得使用内存Zone,那些牵涉NSZone的方法都不得使用
- 不得对一个属性变量的取值方法命名以new开头
- outlet均用weak关键字修饰,除非它是xib中最顶部的界面元素,则需要strong
- Core Foundation不适合ARC,该创建的仍创建,该释放的仍释放
- //API Reference
Block【OC】
- block概念
- 是OC中特有的一种数据类型,用来保存一段代码的数据类型
- 声明block变量的语法为
- 返回值(^变量名)(参数1,参数2);
- 为block变量赋值代码段语法为
- 返回值类型 (^block变量名称)(参数列表) = ^返回值类型 (参数列表) { 执行的代码 };
- ^为shift+6
- 分类
- 目前来讲,一共有4种
- 无参无返回值
- 有参无返回值
- 无参有返回值
- 有参有返回值
- 用法
- block可以作为函数的参数
- block可以作为方法的参数
- block与函数的异同点
- 相同点
- 都可以封装一段代码
- 不同点
- block是一个数据类型
- 可以声明变量
- 也可以作为函数或方法的参数
- 也可以做为函数和方法的返回值
- 但函数做不到上边的三点
- 注意点 //注意点比较多,酌情记
- 如果一个代码段没有参数,是可以省略掉参数列表的小括号
- 代码的返回值类型可以直接省略,因为编译器可以自动检测返回值类型
- 参数列表里可以不写参数的名称,只写参数的类型和数量,但我们平常是使用block时,最好使用完整的格式书写
- typedef可以简化声明block的声明语法
- 具体为 typedef 返回值类型(^变量名)(参数1,参数2.....);
- 这时就可以使用变量名来声明block变量
- 但在实际开发中很少使用typedef
- block代码块的内部可以直接访问并修改全局变量,但只能访问局部变量,若想修改局部变量,需要在变量前加上__block进行修饰
- block访问全局变量时比较简单,变量的地址不变,block内部可以直接修改这个全局变量的值
- block访问局部变量规则相对比较复杂
- 因外部的局部变量存储在栈区,在block代码内部访问这些变量时会把该变量拷贝到堆区,以常量的形式进行拷贝,所以此时,常量的值是无法修改的,也就是block无法直接修改局部变量的值
- 若是在外部的局部变量被__block修饰,那么此时在block代码块内部访问此变量时,同样是拷贝到堆区,但此时是以指针的形式进行拷贝,所以,此时可以修改局部变量的值
- 如果在block内部定义了一个和外部变量相同的局部变量,那么在block内部屏蔽对外部变量的访问
- block内部创建的变量,如果是C的基本数据类型是存储在栈区,如果是OC对象,那么是存储在堆区,这点和在外部的变量存储方式
- MRC模式下
- 如果在block内部没有访问外部的局部变量,那么block是存储在全局区
- 如果访问了,那么会存储到栈区
- ARC下
- 如果内部没有访问外部的局部变量,那么block也是存储在全局区(__NSGlobalBlock__)
- 如果访问了,那么会存储到堆区(__NSMallocBlock__)
协议,延展,代理,分类【OC】
- 概念
- 协议(protocol)
- 是OC中特有的语法,使用@protocol关键字进行声明,专门用来写方法的声明,好比是外包
- 代理(delegate)
- OC特有语法,定义一个协议,声明代理需要具有的方法
- 延展(extension)
- OC特有语法,延展是一个匿名的分类,没有名字,只有声明,和本类共享一个实现
- 分类(category)
- OC特有语法,在不修改源码的情况下,为某一些类扩充一些方法
- 作用
- 协议作用
- 协议是为了让类遵守的,一个类如果遵守了某个协议,就默认拥有了该协议中的所有方法的声明,直接实现即可
- 代理作用
- 使协议对指针指向的对象类型进行限制,以完成特定的功能
- 延展作用
- 一个类的私有声明,专门用来声明私有的方法和成员变量
- 分类作用
- 将一个类分为多个模块,将类似的方法放在同一个模块中,方便后期的维护和整理
- 协议的@required和optional
- @required修饰的方法是强制实现的,如果没有实现,编译器会进行警告,是默认属性
- 被@optional修饰的方法为可选实现,未实现的话,编译器也不会有警告,但如果这个方法是可选实现,在调用这个方法之前之前需要对这个代理进行判断
- 代理设计模式
- 定义一个协议,声明代理需要具有的方法
- 找到使用代理的类
- 定义一个id类型的属性,一般命名为delegate,使协议对指针指向的对象类型进行限制
- 定义一个对象方法,在其中调用代理方法,完成某些特定的功能
- 需要创建作为代理的类
- 这个类需要遵守协议
- 并实现协议中声明的方法
- 注意点
- 协议
- 协议中只能写方法的声明,不能写实现
- 协议不能声明成员变量
- 协议中可以使用@property
- 但只会生成对应的getter和setter方法的声明,没有实现
- 不会生成_开头的成员变量
- 如果一个类遵守了一份协议,协议中的方法没有实现的话,调用该方法,编译阶段没有问题,但运行时程序就会崩溃,因为找不到该方法的实现
- 协议可以多遵守,即一个类可以遵守多个协议
- 代理
- 在代理具有的方法中,若含有@optional修饰的方法,虽然未实现编译器不会做任何警告,但在调用此类方法时,需要预先做判断,若未实现进行调用,程序会崩溃
- 延展
- 延展天生是作为类的私有声明来使用的,不会单独占一个文件,一般写在本类的.m文件中
- 在本类中的声明中(即.h文件中)可以做的事情,在延展中都能做
- 写在延展中的成员变量和方法,只能在当前类内部访问,不能在外部访问
- 分类
- 分类中不能添加成员变量,但可以访问本类的成员变量
- 分类中可以重写与本类同名的方法,编译器会有警告说重复实现,并且会优先调用分类中的方法
- 若有多个分类中同时重写本类中已经实现过的过的方法,会优先调用最后参与编译的一个分类中的实现
- 知识点扩展
- 非正式协议
- 给系统的类添加的分类叫做非正式协议
- 即为系统的类扩充新的方法
- 协议与继承的区别
- 协议中只能声明方法,不能声明成员变量,也不能实现方法
- 协议只管声明方法,不管方法的实现,只要一个类遵循了这个协议,就相当于拥有了这个协议所有方法的声明,同时子类也一样
- 只要一个类继承另一个类,子类就会拥有父类所有的属性和方法
- 若子类中重写了从父类继承而来的方法,那么会优先执行子类重写后的方法,调用方法时,优先找子类再找父类,一层一层,直到NSObject根类
- 某种程度上来讲,OC中的协议可以看做是对OC不能多重继承的补充
- 冒号是继承,尖括号是遵守协议
- 继承的子类对父类有很强的依赖性
结构体和类的区别
- 结构体概念
- 由多个小变量组合而成,结构体中只能有属性
- 类的概念
- 对某些具有相同特征和相同行为的事物的统称
- 区别
- 结构体
- 结构体中的变量存储在栈区,对象存储在堆区
- 只有属性,并且是轻量级的数据使用结构体
- 如果属性很多,那么类比较合适
- 如果封装的数据既有属性也有方法,则只能使用类
@property参数
参数 |
使用范围 |
释义 |
备注 |
assign |
赋值方式 |
不复制不保留,直接赋值 |
默认值 , 基本数据类型和本类不直接拥有的对象 |
retain |
赋值方式 |
将新值保留一份覆盖原值 |
大部分对象可使用 |
copy |
赋值方式 |
将新值复制一份覆盖原值 |
字符串和词典对象选择使用 |
readwrite |
读写权限 |
生成getter和setter两个方法 |
默认值 , 变量可读取可修改 |
readonly |
读写权限 |
只生成getter方法 |
变量只可读取不可修改 |
atomic |
原子性 |
原子操作* |
默认值 , 多线程环境下安全的存取值 |
nonatomic |
原子性 |
非原子操作 |
不生成多线程的同步内容 |
setter |
存取方法 |
自定义赋值方法的名称 |
几乎不使用的一个参数(因违背Apple规范) |
getter |
存取方法 |
自定义取值方法的名称 |
只有在方法返回值为BOOL类型时使用 |
以上是关于C和OC的基础语法(易混淆的概念知识)的主要内容,如果未能解决你的问题,请参考以下文章