Objective-C基础学习笔记
Objective-C基础学习笔记
- day01-基础语法
- NSString
- NS前缀
- 如何定义类
- 1)类的三要素
- 2)定义类的语法
- 3)注意
- 创建类的对象
- 使用对象
- 方法的声明和调用
- 定义
- 无参数方法
- 1)声明
- 2)实现
- 3)调用
- 带1个参数的方法
- 1)声明
- 2)实现
- 3)调用
- 带n个参数的方法
- 1)声明
- 2)实现
- 3)调用
- 带参方法声明规范
- day02-类与对象
- 类加载
- nil与NULL
- 分组导航标记
- 方法与函数
- 函数的声明,实现,调用
- 常见错误-九条
- 多文件开发
- 如何移动文件
- 定义枚举类型
- 对象作为类的属性
- day03-封装
- 异常处理
- 分类
- 处理语法
- 02-类方法
- 03-NSString
- 04-匿名对象
- setter & getter
- day04-继承
- 01-Xcode文档安装
- 02-static关键字
- 03-self关键字
- 04-继承
- 05-继承的特点
- 06-super关键字
- 07-访问修饰符
- 08-私有属性和私有方法
- 私有属性
- 私有方法
- 09-里氏替换原则--LSP
- 10-方法重写
- 11-description方法
- day05-特有语法
- 01-继承的本质
- 02-结构体与类比较
- 03-类的本质
- 04-如何拿到存储在代码段中的类对象
- 05-SEL
- 点语法
- @property
- @synthesize
- @property增强
- 动态类型和静态类型
- id指针
- 动态类型检测
- 构造方法
- day06-内存管理
- 内存管理概述
- 内存管理原则
- 野指针
- 内存&对象回收的本质
- 僵尸对象
- 单对象内存管理
- 多对象内存管理
- 阶段一(直接看阶段三)
- 阶段二
- 阶段三
- 特别注意
- @property参数
- 概述
- atomic&nonatomic
- assign&retain
- readwrite&readonly
- getter&setter
- @class 避免循环引用
- MRC下的循环引用
- day07-ARC与分类
- 自动释放池(针对MRC)
- 类方法规范两则
- 微博练习
- ARC机制
- ARC下的多对象内存管理
- ARC下的循环引用
- ARC兼容MRC
- MRC转ARC
- ARC和垃圾回收机制的区别
- 分类category
- 作业一电商App
- 作业二 微博
- day08-block与协议
- 延展
- block
- 初见
- block简写
- typedef简化复杂的数据类型定义
- block访问变量问题
- block作为函数参数
- block作为参数的应用案例一
- block作为参数的应用案例二
- block作为方法\函数返回值时,类型一定要用typedef定义【基本不用】
- block与函数的异同
- protocol协议
- 代理设计模式
- Foudation框架
- NSString
- 概述
- 格式控制符复习:
- NSString的恒定性
- NSString最最最常用方法
- 字符串的读写 IO
day01-基础语法
NSString
OC字符串的占位符是%@
NS前缀
NextStep —> Cocoa —> Foundation框架中
如何定义类
1)类的三要素
名字,特征,行为
2)定义类的语法
a.位置:源文件中,不写在main中。
b.类的定义
//类的声明@interface 类名 : NSObject {具有的共同特征,定义为变量
}功能是方法,方法声明写这里@end//类的实现@implementation 类名方法实现写这里@end
3)注意
类中属性名必须以_开头,如 _name
创建类的对象
语法:
类名 *对象名 = [类名 new];
Person *p = [Person new];
使用对象
如何访问对象属性:
1)默认下,对象的属性不允许外界直接访问
若要访问,在声明属性的大括号内先加 @public 关键字
2)访问属性的方式
对象名->属性名 = 值;
对象名->属性名;
(*对象名).属性名;
方法的声明和调用
定义
//类的声明@interface 类名 : NSObject {具有的共同特征,定义为变量
}功能是方法,方法声明写这里@end//类的实现@implementation 类名方法实现写这里@end
无参数方法
1)声明
a.位置:在@interface大括号外
b.语法:- (返回值类型)方法名;
2)实现
a.位置:在@implementation中实现
b.语法:
(void)方法名{
实现的代码
}
3)调用
a.通过对象调用
b.语法:[对象名 方法名];
带1个参数的方法
1)声明
a.位置:在@interface大括号外
b.语法:
- (返回值类型)方法名:(参数类型)形参名;
- (void)eat:(NSString *)foodName;
方法名叫 eat:
c.函数的声明
void eat(NSString *foodName);
2)实现
a.位置:在@implementation中实现
b.语法:方法声明复制到@implementation中,去掉分号并追加大括号,方法的实现代码写在括号内。
3)调用
a.通过对象调用
b.语法:[对象名 方法名:实参];
带n个参数的方法
1)声明
a.位置:在@interface大括号外
b.语法:
- (返回值类型)方法名:(参数类型)形参名1 :(参数类型)形参名2 :(参数类型)形参名3;
- (int)sum:(int)num1 :(int)num2;
方法名叫sum: :
2)实现
a.位置:在@implementation中实现
b.语法:方法声明复制到@implementation中,去掉分号并追加大括号,方法的实现代码写在括号内。
3)调用
a.通过对象调用
b.语法:[对象名 方法名:实参1 :实参2 :实参3];
带参方法声明规范
1)带1个参数时,方法名起为 xxxWith:
2)带N个参数时,方法名起为 方法名With:(参数类型)形参名1 and:(参数类型)形参名2 and:(参数类型)形参名3
day02-类与对象
类加载
- 内存结构
栈 存储局部变量
堆 程序员手动申请的字节空间 malloc calloc realloc函数
BSS段 存储未初始化的全局变量,静态变量
数据段(常量区) 存储已被初始化的全局变量、静态变量、常量数据
代码段 存储代码,但是属性只有在创建对象后才会创建在对象中
类加载
1)在声明一个类的指针变量时会访问类;
2)创建对象时会访问类;
程序运行时,当某个类第一次被访问时,将这个类存储到内存中的代码段区域,这个过程叫类加载。
只有类在第一次被访问时才会做类加载,一旦类加载到代码段,直到程序结束才会被释放。
对象在内存中如何存储
假设下面这个写在函数之中.
Person *p1 =[Person new];
1).Person *p1;
会在栈内存中申请1块空间。在栈内存中声明1个Person类型的指针变量p1。
p1是1个指针变量。那么只能存储地址.
2).[Person new];
真正在内存中创建对象的其实是这句代码.
new
做的事情
a.在堆内存中申请1块合适大小的空间.
b.在这个空间中根据类的模板创建对象。
类模板中定义了什么属性,就把这些属性依次的声明在对象之中。
对象中还有另外1个属性叫做isa是1个指针。指向对象所属的类在代码段中的地址。
c.初始化对象的属性
如果属性的类型是基本数据类型那么就赋值为0
如果属性的类型是C语言的指针类型那么就赋值为NULL如果属性的类型是0C的类指针类型那么就赋值为nil
d.返回对象的地址。注意
a.对象中只有属性,而没有方法,自己类的属性外加1个isa指针指向代码段中的类。b.如何访问对象的属性
指针名->属性名;
根据指针找到指针指向的对象再找到对象中的属性来访问。
c.如何调用方法
[指针名方法名];
先根据指针名找到对象,对象发现要调用方法再根据对象的isa指针找到类。然后调用类里的方法。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-htbZ0icn-1655305045670)(https://tva1.sinaimg.cn/large/e6c9d24egy1gznp6whg18j20t90ay754.jpg)]
- 为什么不把方法存储在堆内存的对象中,而是放在代码段中?
每个对象的方法实现的代码都是一样的,这样做可以减少冗余。多个对象的isa值是一致的。
- 对象属性的默认值
创建对象后即使不赋值,属性也是有值的。
基本类型默认是0,C指针类型默认是NULL,OC指针类型默认是nil。
nil与NULL
NULL与nil
只能做指针变量的值。表示该指针不指向内存中的任何一块空间。等价于0,是一个宏。
C指针用NULL,表示不指向任何空间。OC的类指针用nil,表示不指向任何对象。
如果一个类指针的值为nil,表示不指向任何对象。
Person *p = nil;
通过p指针访问对象属性时,运行报错。
通过p指针访问对象方法时,运行不报错,但方法不执行。
分组导航标记
#pragma mark 分组名
添加标题
#pragma mark -
添加水平线
#pragma mark - 分组名
先有水平线再有组名
方法与函数
我们之前在C中学习的函数,就叫做函数
void test(){}
在OC类中写时方法.就叫做方法。
- (void) sayHi;
- 相同点:
都是用来封装1段代码的。将1段代码封装在其中,表示1个相对独立的功能函数或者方法只要被调用.那么封装在其中的代码就会被自动执行. - 不同点:
- 语法不同,
- 定义的位置不一样.
0C方法的声明只能写在@interface的大括弧的外面,实现只能写在@implementation之中,函数除了在函数的内部和@interface的大括弧之中其他的地方都是可以写。
就算把函数写在类中这个函数仍然不属于类所以创建的对象中也没有这个函数,
注意;函数不要写到类中.虽然这样是可以的但是你千万不要这么做因为这么做是极度的不规范的. - 调用的方式也不一样.
函数可以直接调用.
但是方法必须要先创建对象通过对象来调用。 - 方法数是属于类的。有1个伐木累。有个归属感。
而函数是1个孤魂野鬼,就是1个独立的.
函数的声明,实现,调用
#import <Foundation/Foundation.h>
void test();int main(int argc, const char * argv[]) {test();return 0;
}//实现可以直接写在声明中,但一定要写在调用之前!
void test(){NSLog(@"shuchu");
}
常见错误-九条
- @interface是类的声明。@implementation是类的实现他们之间不能相互嵌套.
- 类必须要先声明然后再实现
- 类的声明和实现必须都要有就栓没有方法类的实现也不必不可少的。
- 类的声明必须要放在使用类的前面。
实现可以放在使用类的后面 - 声明类的时候类的声明和实现必须要同时存在,
特殊情况下可以只有实现没社乡明、
虽然可以这样,但是我们平时在写类的时候千万不要这么写因为这么写是极度不规范的会被别人曹晓的。 - 属性名一定要以下划线开头这是规范。否则后面的知识点你就对不上号。
类名每1个单词的首字母大写。 - 属性不允许声明的时候初始化
在为类写1个属性的时候不允许在声明属性的时候为属性赋值。 - OC方法必须要创建对象通过对象名来调用
- 方法只有声明没有实现
a.如果方法只有声明没有实现编译器会给1个警告不会报错。
b.如果指针指向的对象有方法的声明而没有方法的实现那么这个时候通过指针来调用这个方法
在运行的时候就会报错。
多文件开发
所有的类都写在main.m这个源文件之中的.
后果:后期的维护就非常的不方便.也不利于团队开发推荐的方式
把1个类写在1个模块之中,而1个模块至少包含两个文件。.h头文件
写的类声明因为要用到Foundation框架中的类 NSObject 所以在这个头文件中要引入Foundation框架的头文件。然后将类的声明的部分写在.h文件中
.m实现文件
先引入模块的头文件这样才会有类的声明再写上类的实现。如果要用到类,只需要引入这个了模块的头文件就可以直接使用了。
添加类模块的更简洁的方式
NewFile->Cocoa Class自动生成模块文件.h .m
自动的将类的声明和实现写好。填写的名称是决定模块文件的名称,类名是可以自己再改的
但是建议模块的文件名和模块中的类名保持一致,这样方便代码的管理.当我们要使用这个类的时候,需要先将这个类的头文件先引进来.才可以使用。
对象可以作为方法的参数,也可以作为方法的返回值。
参数类型是指针对象 类名 *
对象作为方法的参数时,传递的是地址值,方法内部修改指向对象时,会造成实际改变。
如何移动文件
- Show in finder
- 选中后拖到目标文件夹
- “Copy items if needed”&"Create groups"一定要勾选
定义枚举类型
//定义男女枚举类型
typedef enum {//想定义的值GenderMale,GenderFemale
} Gender; //枚举类型名
若写在单独的.h文件中,需要用 #import “文件名” 也可以写在只用一次的文件里,一般也是.h文件。
对象作为类的属性
Person *p = [Person new];//人有一个 狗 属性
p->_dog = [Dog new];
p->_dog->_qq = [XiangQuan new];//狗有一个 项圈 属性
p->_dog->_qq->_color = @"blue";//给项圈设置颜色[p->_dog->_qq bLingBLing];//调用项圈闪光方法
day03-封装
异常处理
分类
错误:源代码不符合规范,编译报错。
Bug:可以编译,链接,执行。但结果与预期不一致。
异常:可以编译,链接,执行。但在某种情况下,程序会终止运行,后续代码也不执行。程序崩溃。
处理语法
@try{....
}
@catch(NSException *ex){....
}
可能异常的代码放在try中。发生异常时,不崩溃而是跳转执行catch中代码,catch中代码执行完再往下执行。
catch中只有在异常发生时才执行。所以catch写异常处理代码。
@try…@catch后可跟@finally,这里代码无论是否有异常都执行。
不是所有异常都能捕获,C语言异常就是。【实际】用if来进行判断会出异常的地方。
02-类方法
- 声明
用+声明
- 调用
用类名直接调用 [类名 类方法名];
分析一下类方法和对象方法的调用过程.
类方法的特点
1).节约空间:因为调用类方法不需要创建对象,这样就节约了空间。
2).提高效率:因为调用类方法不需要拐弯直接找到类直接执行类中的类方法.在类方法中不能直接访问属性。
1).属性是在对象创建的时候,跟随着对象一起创建在对象之中.2).类第1次被访问的时候,会做类加载,是把类的代码存储在代码段。
因为属性只有在对象创建的时候才会创建在对象之中。
而类方法在执行的时候.有可能还没有对象。对象都没有你访问个毛的属性。
虽然不能直接访问属性。但是我们可以在类方法中创建1个对象访问这个对象的属性.在类方法中也不能通过self直接调用当前类的其他的对象f方法
因为对象方法只能通过对象来调用而这个时候没有对象。实例方法中可以直接调用类方法。
关于类方法的规范.
1).如果我们写1个类,那么就要求为这个类提供1个和类名同名的类方法。
这个方法创建1个最纯洁的对象返回
因为苹果和高手写的类都遵守这个规范。2).如果你希望创建爱的对象的属性的值由调用者指定那么就为这个类方法带参数,
类名withXXX:
+ (Person*)person{Person *p1 = [Person new];return p1; } + (Person *)personwithName:(NSString *)name andAge:(int)age{Person *p1 = [Person new];p1->_name = name;p1->_age = age;return p1; }
03-NSString
一、NSString打印的格式说明
%@ 指针指向的对象
%p 指针的地址值
二、常用类方法
- +(nullable instancetype)stringWithUTF8String:(const char *)nullTerminatedCString;
instancetype 代表返回的是当前类的对象,即NSString对象
将C语言字符串转换为OC字符串。输入函数接收的是c字符串,用这个转化就行。
+ (instancetype)stringWithFormat:(NSString *)format, …
NSString *s = [NSString stringWithFormat:@"第一个参数就得是带格式符号的%@,%d",@"实力代理",12];
将多个相同或不同类型的变量拼接成一个新的字符串。
int age = 14; NSString *s = @"小王"; NSString *s = [NSString stringWithFormat:@"我是%@,今年%d岁了。", s, age];
三、常用对象方法
length方法 返回值为NSUInteger 就是unsigned long %lu
得到字符串个数,可以处理中文。
得到字符串中指定下标处字符
- (unichar)characterAtIndex:(NSUInteger)index;
unichar就是unsigned short %C 大写的
%c读的是变量中第一个字节的数据,%C读的是前两个字节的数据,%d读的是前四个字节的数据.
判断两个字符串内容是否相同
- (BOOL)isEqualToString:(NSString *)aString;
比较两个字符串的大小
- (NSComparisonResult)compare:(NSString *)string;
NSComparisonResult是枚举,-1 0 1
04-匿名对象
我们之前创建对象的做法.
Person *p1 = [Person new];
让1个指针指向1个对象这个指针就叫做这个对象的名字.匿名对象.
没有名字的对象,如果我们创建1个对象,没有用1个指针存储这个对象的地址。也就是没有任何指针指向这个对象那么这个对象就叫做匿名对象。如何去使用1个匿名对象呢?
因为new实际上1个类方法。这个方法做的事情创建对象(4个步骤)。返回值是创建的对象的地址。[Person new]
这句代码的结果实际上就是创建的那个对象的指针。那我们可以直接使用.
[Person new]->_name = @"jack";
[[Person new] sayHi];
注意点.
匿名对象只能使用1次.
每次创建匿名对象都是不同的对象。
[Person new]->_name = "jack";
创建了1个对象
[Person new]->_age = 18;
又创建了1个对象
[[Person new] sayHi] ;
第3个对象.
有神马用?
1)。如果某个对象的成员只会被我们使用1次,用完之后这个对象再也不需要了那么就可以使用匿名对象.2)。如果方法的参数是1个对象,而调用者为这个参数赋值的对象就是专门来给这个方法传递的
并且这个对象调用者不会使用那么这个时候就可以直接为方法传递1个匿名对象。
setter & getter
属性上的@public去掉
setter
- (void)setAge:(int)age;
实现中写逻辑验证
getter
- (int)age;- (int)age{return _age; }
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-AR7JZlGO-1655305045671)(https://tva1.sinaimg.cn/large/e6c9d24egy1gznp8fbobkj20s60fe78e.jpg)]
只读只写封装
只读:只提供getter,不提供setter
只写:只提供setter,不提供getter
day04-继承
01-Xcode文档安装
Window - developer documentation
02-static关键字
OC中的static关键字用法
a.不能修饰属性和方法
b.可以修饰局部变量
如果局部变量被修饰就会变成静态变量,存储在常量区,方法执行完毕后不被回收,再次执行该方法时继续使用而不是重新声明。
当方法返回是该类的对象时,返回值类型可以写成
instancetype
03-self关键字
- 方法中可以声明与属性同名的变量。
方法中访问该同名的变量时,访问的是局部变量。 - self 与java的this仅有一点点像
可以在对象方法和类方法中使用。
self是1个指针。在对象方法中self指向当前对象。在类方法中self指向当前类.
- self用在对象方法中.
1). self在对象方法中,指向当前对象.
当前对象:谁调用方法谁就是当前对象。
2).在对象方法中,self指向当前对象,然后这又有神马用?
a. 可以使用self显示的访问当前对象的属性. self->属性.代表访问的是当前对象的这个属性.
b.可以使用self来调用当前对象的其他的对象方法.
3).对象方法中使用self的场景.
a.必须使用self的场景.
如果在方法中存在和属性同名的局部变量,
你如果想要访问同名的局部变量,直接写就可以了.你如果想要访问当前对象的同 名属性必须使用self
在对象方法中,如果要调用当前对象的其他的对象方法必须使用self.b.
b.选用self的场景.
在方法中不存在和属性同名的局部变量如果这个时候想要访问当前对象的属性 用不用self效果都是一样的.都是访问的当前对象的属性.
属性要求以下划线开头局部变量不要求以下划线开头按照这个规范来实际上是不会重名的.
- 把slef用在类方法中.
1).类加载。
当类第1次被访问的时候会将类的代码存储在代码区.
代码区中用来存储类的空间也有1个地址.
2).在类方法中 self也是1个指针。指向当前这个类在代码段中的地址.
3).总结一下取到类在代码段中的地址的方式.
a.调试查看对象的isa指针的值.
b.在类方法中查看self的值.
NSLog(@"%p", self);
c.调用对象的对象方法class就会返回这个对象所属的类的Class对象地址
NSLog(@"%p", [对象名 class]);
这两个class方法是自带的。
d.调用类的类方法class就会返回这个类对象的地址
NSLog(@"%p", [类名 class]);
4).有神马用?
可以在类方法中使用self来显示的调用本类的其他的类方法.
使用建议,如果要在当前类方法中调用本类的其他的类方法虽然可以直接使用类名但是建议使用self
对象方法可以声明多次.但是只会认为有1次。
对象方法如果有多次声明只能实现1次否则就会报错。
对象方法之间是不能重名的.
类方法之间也是不可以重名的.
但是,对象方法和类方法是可以重名的.通过类名来调用调用的就是类方法通过对象名来调调用的就是对象方法.注意
1).在对象方法中,self代表当前对象.
所以可以通过self访问当前对象的成员.
在对象方法中 不能使用self调用本类的类方法.
2).在类方法中, self代表当前这个类.
所以,可以通过self调用当前类的其他的类方法.
在类方法中﹐不能使用self访问对象的成员。不能去直接访问属性和调用对象方法.
04-继承
05-继承的特点
单根性
一个类只能有一个父类
传递性
a继承b,b继承c,a同时继承b&c
NSObject类
a.是Foundation框架中的类,在这个类中有一个方法new,用来创建对象,返回值就是新对象的指针。
也就是说如果要创建类的对象,就必须要调用这个new方法。因此需要创建对象的类,就需要直接或间接地继承NSObject.b.NSObject中定义了属性isa指针,所以每个子类对象也都有。
06-super关键字
在子类的类方法和对象方法中调用从父类继承过来的方法。不能用来访问属性
@implementation Student - (void)study{NSLog(@"学习");[self sayHi];[super sayHi]; //调用的是同一个方法 }
super关键字.
可以用在类方法和对象方法之中.
在对象方法中可以使用super关键字调用当前对象从父类继承过来的对象方法.
在类方法中 super关键字可以调用当前类从父类继承过来的类方法.
a.类方法也能被子类继承。父类中的类方法可以使用父类名来调用也可以使用子类名调用.b.在子类的类方法中可以使用super关键字调用父类的类方法.
super只能用来调用父类的对象方法或者类方法不能用来访问属性。
子类从父类继承.
1)相当于子类模板中拥有了父类模板中的所有的成员.
2)创建1个子类对象,仍然是根据子类模板来创建对象.
只不过子类模板中拥有父类模板中的成员.
所以,子类对象中既有子类的成员也有父类的成员.3).super特指这个方法是从父类继承过来的.
super是指当前类或者对象的这个方法是从父类继承过来的.OC中的self&super与Java中不同,因为Java可在类中声明变量时赋值。
/*问题是:我不仅仅要输出局部范围的num,还要输出本类成员范围的num。怎么办呢?我还想要输出父类成员范围的num。怎么办呢?如果有一个东西和this相似,但是可以直接访问父类的数据就好了。恭喜你,这个关键字是存在的:super。this和super的区别?分别是什么呢?this代表本类对应的引用。super代表父类存储空间的标识(可以理解为父类引用,可以操作父类的成员)怎么用呢?A:调用成员变量this.成员变量 调用本类的成员变量super.成员变量 调用父类的成员变量B:调用构造方法this(...) 调用本类的构造方法super(...) 调用父类的构造方法C:调用成员方法this.成员方法 调用本类的成员方法super.成员方法 调用父类的成员方法
*/
class Father {public int num = 10;
}class Son extends Father {public int num = 20;public void show() {int num = 30;System.out.println(num);System.out.println(this.num);System.out.println(super.num);}
}class ExtendsDemo5 {public static void main(String[] args) {Son s = new Son();s.show();}
}
07-访问修饰符
作用:修饰属性,限定对象属性的访问范围。
分类:
@private 被修饰的属性只能在本类的内部访问。只能在本类的方法实现中访问。
@protected 只能在本类和子类的方法实现中访问。
@package 只能在当前框架中访问。了解
@public 可在任意地方被访问。
默认是@protected
子类可以继承父类私有属性,但在子类中无法直接访问从父类继承来的私有属性,父类方法中有私有属性赋值时,子类可正常调用该方法。
修饰符作用范围
从出现到遇到另一个修饰符或者大括弧结束。
使用建议
1). @public 无论何时都不用
2). @private 希望属性只在本类使用。
3). @protected 希望在本类和子类中使用时。【推荐】
08-私有属性和私有方法
私有属性
- 用@private修饰的属性就是私有属性,但是在外界创建对象时Xcode仍然会提示,所以没有完全隐藏起来。
- 在实现类中写定义属性既做到了私有,又可以使外界无法访问
@implementation Person {NSString *_name; //在实现类中默认就是private,即使写别的访问修饰符也都是私有的,外界连提示都没有。int _age;
}
@end
私有方法
方法不写声明只写实现,该方法就是私有方法,只能在本类的其他方法中调用,对外不可见。
09-里氏替换原则–LSP
概念:子类可以替换父类位置,而程序功能不受影响。
LSP作用:
a. 一个指针不仅可以存储本类对象地址还可以存储子类对象地址。
b. 如果一个指针的类型是NSObject,则该指针可以存储任意OC对象的地址。
c. 如果一个数组的元素类型是一个OC指针类型的,该数组可以存储本类和子类对象。
d. 如果一个数组的元素是NSObject指针类型,该数组可以存储任意OC对象。
e. 如果方法的形式参数是一个对象,则既可以传递本类对象,也可以传递子类对象。
NSObject *objs[4];
objs[0] = [Person new];
objs[1] = [Student new];
objs[2] = [Dog new];
objs[3] = @"jjsldl";
- 当父类指针指向子类对象时,只能调用父类中的成员,子类独有成员无法访问。
10-方法重写
子类实现文件中,重写实现代码即可。
当1个父类指针指向子类对象,且子类重写了某方法,通过父类指针调用方法时,调用的的是子类重写方法。
多态(14-杀人游戏是案例)
不同子类由于重写表现出来不同结果。
11-description方法
- %p 打印指针变量的值,地址值
%@ 打印指针指向的对象
输出格式:<对象所属的类名:对象地址>
用%@打印对象时,NSLog函数的底层实现如下:
- 调用传入对象的description方法
- 拿到该方法返回值,返回值是一个字符串
- 输出字符串
description方法定义在NSObject类中。
每个OC对象都有该方法。
方法实现是:返回的字符串格式
@"<对象所属的类名:对象地址>"
希望通过%@打印出对象自定义信息时,就在类的实现文件中重写description方法
- (NSString *)description {return [NSString stringWithFormat:@"姓名:%@ 年龄:%d", _name, _age];
}
day05-特有语法
01-继承的本质
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-3DbxxdQr-1655305045672)(https://tva1.sinaimg.cn/large/e6c9d24egy1gznqpq4f0wj21a70u0ju6.jpg)]
- 创建1个对象,这个对象在内存中是如何分配的.
1).子类对象中有自己的属性和所有父类的属性.
2).代码段中的每1个类都有1个叫做isa的指针,这个指针指向它的父类.
一直指到NSObject
[p1 sayHi]; //假设p1是Person对象.
先根据p1指针找到p1指向的对象,然后根据对象的isa指针找到Person类。
搜索Person类中是否有这个sayHi方法如果有执行
如果没有,就根据类的isa指针找父类。
NSobject中如果没有就报错.
02-结构体与类比较
- 相同点:都可将多个数据封装为一个对象
struct Date {int year;int month;int day;
}ZCDate;@interface Date : NSObject {int year;int month;int day;
}
@end
不同点
1)结构体只能装数据,但类还能封装行为。
2)结构体变量分配在栈空间(如果是局部变量的话),类分配在堆空间。
栈特点:空间小,数据访问效率高。
堆特点:空间大,数据访问效率低。
3)应用场景:带有行为的一定用类。没行为,属性少时用结构体,属性多时用类。
4)赋值语法
结构体 Student 类 Person
Student s1 = {"jack", 19, GenderMale}; Student s2 = s1; //结构体copyPerson *p1 = [Person new]; Person *p2 = p1; //对象地址复制
创建结构体
可以放在所用类文件中,也可以单独放在一个.h文件中,结构体创建时没有需要导入的文件。
typedef struct {int year;int month;int day;
}Date;//当结构体是一个类的属性要赋值时,加一个强转
Book *b1 = [Book new];
[b1 setPublisDate:(Date){1998, 2,2}];
03-类的本质
- 讨论三个问题
1)类什么时候加载到代码段?
类第一次被访问时,类被加载到代码段存储,叫做类加载。
2)类以什么样的形式存储在代码段?
3)类一旦被加载到代码段之后,什么时候回收?
不会被回收,除非程序结束。
- 类以什么样的形式存储在代码段?
1)任何存储在内存中的数据都有一个数据类型
int num = 12;
float 12.12f
'a'
任何在内存中申请的空间也有自己的类型
Person *p1 = [Person new];
2)在代码段存储类的那块空间是什么类型的?
在代码段中存储类的步骤
a.先在代码段中创建1个Class对象,Class是Foundation框架中的1个类。这个Class对象 就是用来存储类信息的。
b.将类的信息存储在这个Class对象之中.
这个Class对象,至少有3个属性
类名:存储的这个类的名称
属性s:存储的这个类具有哪些属性
方法s:存储的这个类具有哪些方法
所以, 类是以Class对象的形式存储在代码段的。
存储类的这个Class对象,我们也叫做类对象。用来存储类的1个对象。
所以,存储类的类对象也有1个叫做isa
指针的属性,这个指针指向存储父类的类对象。
04-如何拿到存储在代码段中的类对象
1).调用类的类方法class就可以得到存储类的类对象的地址。
2).调用对象的对象方法 class 就可以得到存储这个对象所属的类的Class对象的地址。
3).对象中的isa指针的值其实就是代码段中存储类的类对象的地址。
注意:
声明Class指针的时候不需要加 * 因为在typedef的时候已经加了*了。
05-SEL
- SEL全称叫做selector选择器.
SEL是1个数据类型,所以要在内存中申请空间存储数据.SEL其实是1个类.SEL对象是用来存储1个方法的.
- 类是以Class对象的形式存储在代码段之中.
类名:存储的这个类的类名.NSString
还要将方法存储在类对象之中.如何将方法存储在类对象之中:
1).先创建1个SEL对象.
2).将方法的信息存储在这个SEL对象之中.
3).再将这个SEL对象作为类对象的属性.
- 拿到存储方法的SEL对象,
1)。因为SEL是1个typedef类型的在自定义的时候已经加 * 了.
所以我们在声明SEL指针的时候不需要加*
2).取到存储方法的SEL对象,
写到main中,导入相关类的头文件。SEL s1 = selector(方法名);
- 调用方法的本质.
[p1 sayHi];
内部的原理:
1),先拿到存储sayHi方法的SEL对象,也就是拿到存储sayHi方法的SEL数据。SEL消息.
2)。将这个SEL消息发送给p1对象.
3)。这个时候,p1对象接收到这个SEL消息以后就知道要调用方法
4).根据对象的isa指针找到存储类的类对象.
5).找到这个类对象以后在这个类对象中去搜寻是否有和传入的SEL数据相匹配的.
如果有就执行如果没有再找父类直到NSObject
- 0C最重要的1个机制:消息机制.
调用方法的本质其实就是为对象发送SEL消息.
[p1 sayHi];
为p1对象发送1条sayHi消息.
重点掌握:
1).方法是以SEL对象的形式存储起来.
2).如何拿到存储方法的SEL对象.手动的为对象发送SEL消息。
1).先得到方法的SEL数据.
2).将这个SEL消息发送给p1对象.
调用对象的方法将SEL数据发送给对象.
- (id) performSelector: (SEL)aSelector;Person *p1 = [Person new] ;
SEL s1 =selector(sayHi);
[p1 performSelector:s1]; //与[p1 sayHi]效果是完全一样的。
【重点】
1、类是以Class对象的形式存储在代码段.
2、如何取到存储类的类对象.
3、如何使用类对象调用类的类方法
4、方法是以SEL数据的形式存储的.
5、调用方法的两种方式.
点语法
1、与Java相比,语法类似本质不同
2、使用点语法来访问对象的属性,前提是有setter getter方法
语法:
对象名.去掉下划线的属性名;
p1.name = @"jack";
这个时候就会将@"jack"赋值给p1对象的 _name 属性.
NSString *name = p1.name;
把p1对象的 _name属性的值取出来.
3、原理:OC会去调用setter和getter方法
4、注意:在setter和getter方法的实现中不要用点语法,会发生递归使程序崩溃。
setter和getter方法名 一定要符合规范。
@property
1、作用:自动生成getter、setter方法的声明
因为是生成方法的声明,所以应该写在@interface类的声明之中。
2、语法:
property 数据类型 名称; //去掉下划线,该名称决定getter和setter方法的名字
@property int age;
3、原理:
编译器在编译的时候,会根据@property生成getter和setter方法的实现。
@property 数据类型 名称; //等价于下面两句
- (void)set首字母大写的名称:(数据类型)名称;
- (数据类型)名称;@property int age; //等价于下面两句
- (void)setAge : (int)age;
- (int)age;
@synthesize
1、作用:自动生成getter、setter方法的实现
因为是生成方法的实现,所以应该写在@implementation中。
2、语法:
@synthesize @property名称;
@synthesize age;
3、如果对方法有额外要求,可重写实现。
生成的是一个真私有属性。
@property增强
@property NSString *name;
现有Xcode版本中,只需要写1个@property,编译器就会自动:
1)生成真私有属性。属性的名称自动的加1个下划线。
2).生成getter setter的声明
3).生成getter setter的实现。
做的事情:
getter、setter方法的实现都是最简单的,如有要求可对其进行重写。
但是,若同时重写getter、setter方法,就不会自动生成私有属性。
注意:
继承中,由于自动生成的属性是私有的,子类不能直接访问。但可以借助super或self通过父类或子类的getter、setter访问
动态类型和静态类型
- 0C是1门弱语言.
编译器在编译的时候,语法检查没有那么严格,不管你怎么写都是可以的。
int num = 12.12; //都不报错
NSString *s = [Person new];
静态类型:指的是1个指针指向的对象是1个本类对象。
动态类型:指的是1个指针指向的对象不是本类对象,(比如向上转型写法,OC中无向上转型概念)NSString *s = [Person new]; //这也是动态类型,但编译期不报错
编译检查
编译器在编译的时候,能不能通过1个指针去调用指针指向的对象的方法。
判断原则:看指针所属的类型之中是有这个方法,如果有就认为可以调用编译通过。
如果这个类中没有那么编译报错。
这个叫做编译检查,在编译的时候能不能调用对象的方法主要是看指针的类型。NSString *s = [Person new];
[s length]; //Person没有length方法,但NSString有,所以检查通过。
可以通过强转类型来骗过编译器,比如[
[(Pig *)str eat];
运行检查
运行时会检查对象是否有该方法。
id指针
NSObject
是OC中所有类的基类。根据LSP(里氏替换原则), NSObject指针就可以指向任意的OC对象。所以,NSObject指针是1个万能指针。可以执行任意的OC对象。
缺点:如果要调用指向的子类对象的独有的方法。就必须要做类型转换。id指针
是1个万能指针,可以指向任意的0C对象.
- id是1个typedef自定义类型在定义的时候已经加了*
所以,声明id指针的时候不需要再加*了.
- id指针是1个万能指针,任意的0C对象都可以指.
- id是1个typedef自定义类型在定义的时候已经加了*
NS0bject和id的异同.
相同点:万能指针都可以执行任意的OC对象.**不同点:**通过NSObject指针去调用对象的方法的时候。编译器会做编译检查。
通过id类型的指针去调用对象的方法的时候,编译器直接通过,无论你调用什么方法。注意:id指针只能调用对象的方法。不能使用点语法,如果使用点语法就会直接报编译错误。
父类中的类方法创建1个父类对象返回.
1)如果返回值写为父类类型的,那么子类来调用这个方法得到的就是父类指针。
解决的方式:把返回值改为id类型的。
2)方法的内部创建的对象的是不要写死。写成return [self new];
3)方法的返回值是id类型的。问题就是任意指针都可以接收这个方法的返回值,编译器连个警告都没有。
如果方法的返回值是instancetype
,代表方法的返回值是当前这个类的对象。使用建议
1). 如果方法内部是在创建当前类的对象,不要写死成类名[类名 new];
而是用self代替类名。
2). 如果方法的返回值是当前类的对象,也不要写死了,而是写instancetype
id和instancetype的区别.
1、instancetype只能作为方法的返回值,不能在别的地方使用。
id既可以声明指针变量,也可以作为参数,也可以作为返回值。
2、instancetype 是1个有类型的代表当前类的对象。
id是1个无类型的指针,仅仅是1个地址,没有类型的指针。
动态类型检测
1、编译检查可以骗过去,不代表程序没问题。
2、希望有进一步检测手段。
1).判断对象中是否有这个方法可以执行【最常用】
- (BOOL)respondsToSelector: (SEL)aSelector;
Person *p1 = [Person new];
BO0L b1 = [p1 respondsToSelector:@selector(length)];
if(b1 == YES){[p1 sayHi];
}
else{
NSLog(@"NO. . . ..");
}
2).判断指定的对象是否为指定类的对象或者子类对象.
- (BOOL)isKindOfClass: (class)aclass;Student *s1 = [Student new];
B00L b1 = [s1 isKindOfClass:[Person class]]; //判断s1对象是否为Person对象或者Person的子类对象.
3). 判断对象是否为指定类的对象不包括子类.
- (BOOL)isMemberOfClass: (class )aclass;[s1 isMemberOfClass:[Student class]]; //判断s1对象是否为1个Student对象
4). 判断类是否为另外1个类的子类.
+(BOOL)isSubclassofClass:(class)aclass;
构造方法
option+点击
- 引入
alloc方法是1个类方法,作用:哪1个类调用这个方法就创建那个类的对象,并把对象返回。
init方法是1个对象方法,作用:初始化对象.
创建对象的完整步骤:
应该是先使用alloc创建1个对象,然后再使用init初始化这个对象才可以使用这个对象。
Person *p1 = [[Person alloc] init]; 等价于 Person *p1 = [Person new];
- init方法
作用:初始化对象,为对象的属性赋初始值,这个init方法我们叫做构造方法。
init方法做的事情:初始化对象;为对象的属性赋默认值。
如果属性的类型是:基本数据类型->0 C指针->NULL 0C指针->nil
所以,我们创建1个对象如果没有为这个对象的属性赋值,这个对象的属性是有默认值的。所以,我们每次新创建1个对象,这个对象的属性都被初始化了。
- 重写init方法的规范:
1)、必须要先调用父类的init方法.然后将方法的返回值赋值给self
2)、调用init方法初始化对象有可能会失败,如果初始化失败.返回的就是nil
3)、判断父类是否初始化成功。判断self的值是否为nil 如果不为nil说明初始化成功.
4)、如果初始化成功就初始化当前对象的属性.
5)、最后返回self的值.
- 解惑
1).为什么要调用父类的init方法?
因为父类的init方法会初始化父类的属性。所以必须要保证当前对象中的父类属性也同时被初始化.
2).为什么要赋值给self ?
因为,调用父类的init方法会返回初始化成功的对象
实际上返回的就是当前对象。但是我们要判断是否初始化成功.
- (instancetype)init{if(self = [super init]){//初始化当前类的属性的代码self.name = "jack";}return self;
}
- 自定义构造方法
重写init方法以后:这样每次创建出来的对象的属性的值都是一样的。
现在想创建对象的时候,对象的属性的值由创建对象的人来指定,而不是写死在init方法中。
- 自定义构造方法.规范:
1).自定义构造方法的返回值必须是instancetype
2).自定义构造方法的名称必须以initwith
开头
3).方法的实现和init的要求一样.
//实现,位置可在对象方法后面,无所谓
- (instancetype)initwithName:(NSString *)name andAge:(int)age{if(self = [super init]){self.name = name;self.age = age;}return self;
}
//调用
Dog *d1 = [[Dog alloc] initwithName:@"小黄" andAge:2];
day06-内存管理
内存管理概述
内存管理
内存的作用:存储数据。
1)如何将数据存储到内存之中。
声明1个变量,然后将数据存储进去。
2)当数据不再被使用的时候,占用的内存空间如何被释放。内存中的五大区域
栈:局部变量。当局部变量的作用域被执行完毕之后,这个局部变量就会被系统立即回收。
堆:OC对象,使用C函数申请的空间.
BSS段:未初始化的全局变量、静态变量。一旦初始化就回收并转存到数据段之中
数据段:已经初始化的全局变量、静态变量。直到程序结束的时候才会被回收
代码段:代码。程序结束的时候,系统会自动回收存储在代码段中的数据。
栈、BSS段、数据段、代码段存储在它们中的数据的回收,是由系统自动完成的.不需要我们干预。
内存管理范围
存储在堆中的OC对象系统不会自动回收,直到程序结束才回收。
因此只需要处理堆中OC对象的回收,其他位置的数据系统自动管理。
回收时机:没有任何人使用该对象时。
引用计数器
1)每个对象都有1个retainCount属性,类型是unsigned long,占8个字节。
记录该对象有多少人用。创建对象后便初始化为1。2)当引用计数器变为0时表示无人使用,系统自动回收。
如何操作引用计数器
1)为对象发送1条
retain
消息。对象的引用计数器就会加1,当多1个人使用对象的时候才发.
2)为对象发送1条release
消息.对象的引用计数器就会减1.当少1个人使用对象的时候才发。
3)为对象发送1条retainCount
消息。就可以去到对象的引用计数器的值。
4)对象被回收时,自动调用对象的dealloc
方法。内存管理分类
MRC: Manual Reference Counting 手动引用计数
程序员手动发消息进行计数器的增加操作。
2011年的iOS5之前只有MRCARC: Automatic Reference Counting 自动引用计数
系统自动在合适的地方发送retain & relase 消息
Xcode4.2开始支持ARC,且Xcode7默认支持ARC**如何关闭ARC:**点击项目根目录,点击要关闭ARC的target,在Build Settings中搜索auto/Automatic Reference Counting,设置成no。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-efe1ehu8-1655305045672)(https://tva1.sinaimg.cn/large/e6c9d24ely1gzvngkm3l0j215l09uwgb.jpg)]
ARC机制下那三个方法无法使用。
重写dealloc规范
必须调用父类的dealloc方法,且放在最后一句代码。
- (void)dealloc{//只要这个方法执行了,就代表这个对象被回收了NSLog(@"名字叫做%@的人消失了!", _name);[super dealloc]; }
内存管理原则
内存管理的重点
1)多一个人使用该对象时,先为该对象发送retain消息。2)少一个人使用该对象时,先为该对象发送release消息。
内存管理原则
1)有对象的创建,就要匹配1个release。
2)retain次数和release次数要匹配。
3)谁用谁retain,谁不用谁release
4)多一人使用才retain,少人用才release。
野指针
C语言中的野指针:定义1个指针变量,没有初始化,这个指针变量的值是1个垃圾值,指向1块随机的空间。这个指针就叫做野指针。
OC中的野指针:指针指向的对象已经被回收了。这样的指针就叫做野指针。
内存&对象回收的本质
内存回收:申请1个变量,实际上就是向系统申请指定字节数的空间,这些空间系统就不会再分配给别人了。当变量被回收的时候,代表变量占用的字节空间从此以后系统可以分配给别人使用了。
但是字节空间中存储的数据还在,新申请变量被分配到该字节空间时就会继承这个数据。对象回收:指的是对象占用的空间可以分配给别人。
当该空间没有分配给别人之前,对象数据还在,调用时可能不会出错。
僵尸对象
定义:一个对象已经被释放,但占用的空间还没有分配给别人,这样的对象就是僵尸对象。
问题:通过野指针访问僵尸对象时,可能有问题也可能没有问题。
但默认情况下可以访问僵尸对象。
Xcode有僵尸对象的实时检查机制,开启后,无论该僵尸对象的空间是否被分配都会报错。**开启方法:**在选择运行程序那里选带edit的,选弹窗左边的Run和右边上方的Diagnostics,勾选Zombie表示开启检查,访问僵尸对象就有报错。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7f4BdX7x-1655305045673)(https://tva1.sinaimg.cn/large/e6c9d24ely1gzvndtu5jaj21270kqq5h.jpg)]但是开启检查后,会比较消耗资源。
使用野指针访问僵尸对象会报错。如何避免僵尸对象错误。
当1个指针称为野指针以后,将这个指针的值设置nil。
当1个指针的值为nil,通过这个指针去调用对象的方法时,不会报错,只是没有任何反应。但是访问属性时会报错
单对象内存管理
内存泄露。
指的是1个对象没有被及时的回收,在该回收的时候而没有被回收一直驻留在内存中,直到程序结束的时候才回收。单个对象的内存泄露的情况
1)有对象的创建,而没有对应的relase。
2)retain的次数和relase的次数不匹配。
3)在不适当的时候为指针赋值为nil。
4)在方法中为传入的对象进行不适当的retain如何保证单个对象可以被回收
1)有对象的创建就必须要匹配1个relase
2)retain次数和release次数一定要匹配.
3)只有在指针成为野指针的时候才赋值为nil
4)在方法中不要随意的为传入的对象retain.
多对象内存管理
阶段一(直接看阶段三)
当属性是1个OC对象的时候。setter方法的写法:
将传进来的对象赋值给当前对象的属性,代表传入的对象多了1个人使用,所以我们应该先为这个传入的对象发送1条retain消息再赋值。属性是OC对象的对象销毁的时候,代表属性指向的对象少1个人使用。就应该在拥有属性的对象的dealloc中relase该属性对象。
代码写法:
- (void)setcar:(Car *)car{_car = [car retain];
}//这个是在拥有Car的类中
- (void)dealloc{[_car release];[super dealloc];
}
阶段二
当属性是1个OC对象的时候,setter方法照着上面那样写,其实还是有Bug的。当为对象的这个属性多次赋值的时候,就会发生内存泄露。
发生泄露的原因:当为属性赋值的时候,代表旧对象少1个人用,新对象多1个人使用,应该relase旧的,retain新的。
- (void)setcar:(Car *)car{[_car release];//初次赋值时,该属性对象为nil,不报错只是没有反应,程序继续执行!!!_car = [car retain];
}
阶段三
int main(int argc, const char * argv []){Person *p1 = [Person new];Car *bmw = [Car new] ;bmw.speed =100;p1.car = bmw;[bmw release];bmw.speed = 200;p1.car = bmw;//这里导致僵尸对象[p1 release];return 0;
}
出现僵尸对象错误的原因:新旧对象是同一个对象。
解决方案:新旧对象相同时,什么都不做。
最终代码如下:
- (void)setcar:(Car *)car{if(_car != car){//如果新旧对象不是同一个[_car release];//release旧的_car = [car retain];//retain新的}
}//这个是在拥有Car作为属性的类中
- (void)dealloc{[_car release];[super dealloc];
}
特别注意
内存管理的范围是OC对象
只有属性的类型是OC对象是,才如上改写。
NSString * 是OC对象
@property参数
概述
默认的@property在生成的方式实现中,无论什么类型都是直接赋值
@property可以带参数实现变化,每组参数只能用一个,顺序随意:
@property(参数1,参数2,参数3......)数据类型名称;
- 与多线程相关的两个参数。
atomic nonatomic - 与生成的setter方法的实现相关的参数.
assign retain - 与生成只读、读写相关的参数
readwrite readonly - 是与生成的getter setter方法名字相关的参数。
getter setter
atomic&nonatomic
atomic:默认值。生成的setter方法会被加上线程安全锁。
特点:安全、效率低下
nonatomic:生成的setter方法不会加线程安全锁。
特点:不安全,但是效率高。
建议:要效率就使用nonatomic
在没有讲解多线程的知识以前统统使用nonatomic
assign&retain
与生成的setter方法的实现相关的参数。
assign:默认值。setter方法的实现是直接赋值。
retain:setter方法的实现是标准的MRC内存管理代码。
当属性的类型是0C对象类型的时候,那么就使用retain
当属性的类型是非OC对象的时候,使用assign.
千万注意:
retain参数时,只是生成标准的setter方法为标准的MRC内存管理代码不会自动的在dealloc中生成relase的代码。所以,我们还要自己手动的在dealloc中release。
readwrite&readonly
readwrite:默认值。
readonly:只生成getter
getter&setter
默认情况下@property生成的getter&setter方法的名字都是最标准的名字。
getter = getter方法名字
setter = setter方法名字 setter方法是带参数的所以要加1个冒号。
记住:如果使用getter setter修改了生成的方法的名字。
在使用点语法的时候,编译器会转换为调用修改后的名字的代码
1)无论什么情况都不要改setter方法的名字,因为默认情况下生成的名字就已经是最标准的了。
2)当属性的类型是1个BOOL类型的时候,修改以提高可读性,如isGoodMan
@class 避免循环引用
当两个类相互引入头文件时。就会出现循环引用的问题。发生无限递归,而导致无法编译通过。
解决方案:
其中一边不要使用#import引入对方的头文件。
在.h文件中用@class类名;
来标注这是1个类,这样子就可以在不引入对方头文件的情况下,告诉编译器这是1个类。在.m文件中再用#import引入头文件。
class与#import的区别
1)#import是将指定的文件的内容拷贝到写指令的地方。
2)@class并不会拷贝任何内容。只是告诉编译器,这是1个类,这样编译器在编译的时候才可以知道这是1个类。
MRC下的循环引用
- 两个对象相互引用时,A对象的属性是B,B对象的属性是A。
如果两边都使用retain就会发生内存泄露。 - 解决方案:1端使用retain 另外1端使用assign,使用assign的那1端在dealloc中不再需要release了。
day07-ARC与分类
自动释放池(针对MRC)
自动释放池的原理。
存入到自动释放池中的对象,在自动释放池被销毁的时候,会自动调用存储在该自动释放池中的所有对象的release方法。可以解决的问题:
将创建的对象存入到自动释放池之中,就不再需要手动的relase这个对象了。因为池子销毁的时候就会自动的调用池中所有的对象的relase。如何创建自动释放池。
@autoreleasepool{}
这对大括弧代表这个自动释放池的范围。如何将对象存储到自动释放池之中?
在自动释放池之中调用对象的autorelease方法,就会将这个对象存入到当前自动释放池之中。这个autorealse方法返回的是对象本身。autoreleasepool{Person *p1 = [[[Person alloc]init]autorelease]; }
八大注意
1)只有在自动释放池中调用了对象的autorelease方法,这个对象才会被存储到这个自动释放池之中,如果只是将对象的创建代码写在自动释放之中,而没有调用对象的autorelease方法,是不会将这个对象存储到这个自动释放池之中的。
2)对象的创建可以在自动释放池的外面。在自动释放池之中,调用对象的autorelease方法,就可以将这个对象存储到这个自动释放池之中。
3)如果对象的autorelease方法的调用放在自动释放池的外面,是无法将其存储的这个自动释放池之中的。
4)当自动释放池结束的时候,仅仅是对存储在自动释放池中的对象发送1条release消息而不是销毁对象。
5)如果在自动释放池中,调用同1个对象的autorelease方法多次,就会将对象存储多次到自动释放池之中,在自动释放池结束的时候,会为对象发送多条release消息。有可能产生野指针。
6)如果在自动释放池中,调用了存储到自动释放中的对象的release方法。在自动释放池结束的时候,还会再调用对象的release方法。有可能产生野指针。
7)将对象存储到自动释放池,并不会使对象的引用计数器+1。
8)自动释放池可以嵌套,调用对象的autorelease方法,会讲对象加入到当前自动释放池之中,只有在当前自动释放池结束的时候才会向对象发送release消息。自动释放池的唯一作用就是省略创建对象时手动调用release方法。
在本类中 self.属性名 等同于 _属性名
类方法规范两则
1)提供与自定义构造方法相同功能的类方法,这样可以快速的创建1个对象。
2)创建类时写1个同名的类方法,用来让外界调用类方法来快速的得到1个对象,并且要求这个对象已经被autorelease过了。
规范:
a. 这个类方法以类名开头,如果没有参数就直接是类名如果有参数就是类名withXX:
b. 使用类方法得到的对象,要求这个对象就已经被autorelease过了.
+ (instancetype)person{return [[[self alloc] init] autorelease];
}
这样,我们直接调用类方法.就可以得到1个已经被autorelease过的对象。
微博练习
一、微博类(Microblog)
属性:文字内容、图片、发表时间、作者、被转发的微博、评论数、转发数、点赞数
二、作者类(User)
属性:名称、生日、账号
三、账号类(Account)
属性:账号名称、账号密码、账号注册时间
ARC机制
什么是ARC
Automatic Reference Counting,自动引用计数。即ARC.系统自动计算对象的引用计数器的值。
是wwDC2011和i055引入的最大的变革和最激动人心的变化,ARC是新的LLVM3.0编译器的一项特性。
在程序中使用ARC非常简单,只需要像往常那样编写代码,永远不手动调用retain、release、autorelease、dealloc方法,这是ARC的最基本的原则。
特别注意:ARC是编译器机制。在编译器编译代码的时候,会在适时的位置加入retain、 release和autorealse代码。ARC机制下,对象何时被释放?
只要没有强指针指向这个对象,这个对象就会立即回收。**程序结束前没有放入自动释放池的强指针对象也会回收掉。**情况有: 1)指向对象的强指针被回收;
2)指向对象的强指针被赋值为nil
强指针与弱指针。
强指针:默认情况下,我们声明的指针强指针。或用strong来显示的声明这是1个强指针。
Person *p1;
这是1个强指针。
__strong Person *p2;
使用strong来显示的声明强指针。弱指针:使用
__weak
标识
无论是强指针还是弱指针,都是指针,都可以用来存储地址。
唯一的区别就是在ARC模式下,他们用来作为回收对象的基准。确认程序是否开启ARC机制.
1)默认情况下,Xcode开启ARC机制.
2)ARC机制下,不允许调用retain、relase、retainCount、autorelease方法。
3)在dealloc中不允许[super dealloc];演示第1个ARC案例
int main(int argc, const char *argv[]){autoreleasepool{Person *p1 = [Person new];//指针变量默认是强指针变量,放池子外也回收。NSLog(@"------");//当执行到这里的时候,p1指针被回收,那么Person对象就没有任何强指针指向它了。对象就在这被回收。}return 0; }
ARC下的多对象内存管理
在ARC的机制下,@property参数不能使用retain
因为retain代表生成的setter方法是MRC的标准的内存管理代码。而我们在ARC的机制下不需要这些代码。
所以,在ARC机制下的setter方法什么都不需要做,直接赋值就可以了。ARC机制下,当1个类的属性是1个OC对象时,这个属性应该声明为强类型。
@property参数strong和weak可控制指针强弱,不写就是strong
@property(nonatomic,strong)Car *car;
生成的私有属性car 是1个强类型的.
@property(nonatomic,weak)Car *car;
生成的私有属性car 是1个弱类型的.使用建议
1)在ARC机制下,属性的类型是OC对象类型的,绝大多数用strong2)在ARC机制下,如果属性的类型不是OC对象类型的,使用assign
3)strong和weak都是应用在属性的类型是OC对象的时候。属性的类型不是OC对象的时候就使用assign
ARC下的循环引用
- 两个对象相互引用时,A对象的属性是B,B对象的属性是A。
如果两边都使用strong就会发生内存泄露。 - 解决方案:1端使用strong另外1端使用weak。
ARC兼容MRC
只想使项目中部分文件实现MRC的步骤:1)点击项目。2)点击目标target。3)点击右侧导航栏中的Build Phases 4)点击Compile Sources 5)双击目标.m文件,输入-fno-objc-arc
。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zGKbAgPX-1655305045673)(https://tva1.sinaimg.cn/large/e6c9d24ely1gzwzggqntej219e0c0408.jpg)]
MRC转ARC
支持全代码转ARC,但是转不回去。
代码执行窗口选好目标target,点击Xcode最上方的Edit,选Convert,选To-ARC
【轻易不要这么全代码转换!】
ARC和垃圾回收机制的区别
ARC:是在编译时,在代码中合适的地方操作引用计数器,使对象无人使用时被回收。
GC:是在运行时,通过垃圾回收器不断扫描堆,回收无人用的对象。
分类category
概念
将一个类分为多个模块。
新建文件选择.m文件,添加 模块名、category、本类名
生成的文件名:本类名+分类名.h 本类名+分类名.m
分类的使用.
1)如果要访问分类中定义的成员,就要把分类的头文件引进来。使用分类注意的几个地方:
- 分类只能增加方法不能增加属性
- 在分类之中可以写@property 但不会生成私有属性,也不会自动生成getter setter的实现。只会生成getter setter的声明.
- 在分类的方法实现中不可以直接访问本类的真私有属性(定义在实现类中的属性)
但是可以调用本类的getter setter来访问属性. - 当分类中有和本类中同名的方法的时候,优先调用分类的方法,哪怕没有引入分类的头文件。
如果多个分类中有相同的方法,优先调用最后编译的分类.
总结:
将新的类分为多个模块;为系统类添加方法。
判断字符串中数字的个数
- (int)numberCount{int count = 0;for(int i=0; i<self.length; i++){unichar ch = [self characterAtIndex:i];if(ch>='0' && ch<='9'){count++;}}return count;
}
作业一电商App
要求:利用OC+面向对象设计下面的三个类:
一、商品类Goods
属性:商品名称、单价、重量、商品展示图片、生产日期produceDate、过期日期expireDate
二、买家类(用户)Buyer
属性:姓名、性别(枚举)、年龄、身高〔单位: cm)
三、卖家类Seller
属性:姓名、性别(枚举)年龄、身高〔单位: cm)、所出售商品(假设一个卖家就卖一件商品)
四、买家类、卖家类〔抽象父类Person)
五、在main函数中创建卖家、商品、买家类的对象。
作业二 微博
属性参照微博练习
模拟场景;
1)张三在2007-9-8 17: 56:34的时候,注册了一个账号(名称: itcast,密码:123456)
2)张三的生日是1998-7-418:46:24
3)张三在2010-8-8 9: 23:44的时候。发布一条微博
-文字内容 今天心情不错
-图片 @“goodDay. png”
-发表时间
-作者
-被转发的微博
-评论数100
-转发数 290
-点赞数2088
4)李四在2006-9-8 19: 26:54的时候,注册了一个账号(名称: lisiitcast,密码:654321)
5)李四的生日是1999-9-614:16:28
6)李四在2011-8-8 20:47∶09的时候,转发了张三之前发布的微博,并且还附带了一句话:@“今天心情确实不错""
-评论数10
-转发数20
-点赞数200
day08-block与协议
延展
延展:Extension
1)是一个特殊的分类,所以延展也是类的一部分
2)特殊之处:
a)延展这个特殊的分类没有名字;
b)只有声明没有实现,和本类共享1个实现。
c)专门用来私有化成员变量
延展的语法
@interface 本类名 () @end
延展和分类的区别
1)分类有名字,延展没有名字是1个匿名的分类。
2)分类有单独的声明和实现。延展只有声明没有单独的实现和本类共享1个实现。
3)分类中只能新增方法。而延展中任意的成员都可以写。
4)分类中可以写@property但是只会生成getter setter的声明。
延展中写@property会自动生成私有属性也会生成getter setter的声明和实现。延展的应用场景
1)延展100%的情况下不会独占1个文件。都是将延展直接写在本类的实现文件中。
这个时候,写在延展中的成员,就相当于是这个类的私有成员,只能在本类的实现中访问,外部不能访问。
2)使用举例:使用延展为类定义私有成员。将延展定义在这个类的实现文件中。
如果想要为类写1个真私有属性,虽然我们可以定义在@implementation之中,但是不要这么写这样很不规范。写1个延展,将这个私有属性定义在延展中。
如果要为类写1个私有方法,建议将声明写在延展中,实现写在本类的实现中。提供代码的阅读性。
如果想要为类写1个私有的@property就直接写在延展里,此时getter,setter也只能在本类访问。
block
初见
block是一个数据类型,block类型的变量中专门存储一段代码,可以有参数也可以有返回值.
//声明
//返回值类型 (^block变量的名称)(参数列表);
void (^myBlock1)();//声明了1个叫做myBlock1的block类型变量,这个变量中只能存储没有返回值没有参数的代码段。
int (^myBlock3)(int num1,int num2);//代码段初始化
^返回值类型(参数列表){代码段;
}
^void(){NSLog(@"我爱你");
}
//举例1
void (^myBlock1)();
myBlock1 = ^void(){NSLog(@"我爱你");NSLog(@""我恨你");
}void (^myBlock1)() = ^void(){NSLog(@"我恨你");
}
//举例2
int (^myBlock2)() = ^int(){int num = 12 + 21;retrun num;
}
//执行
block变量名(...参数列表);
myBlock1();
block简写
//代码段的void可以省略
void (^myBlock1)() = ^(){
NSLog(@"我爱你");
};
//无参数代码段的小括弧写可以省略
int (^myBlock2)() = ^int{int num1 = 10 + 20;return num1;
};
//声明block变量有参数时,可以省略参数名
int (^myBlock3)(int, int) = ^int(int num1, int num2){int result = num1 + num2;retrun result;
};
/**
代码段中的返回值,无论有无都可以省略。
系统判断依据:有return则认为是其后的数据类型;没有则认为是无返回值。
*/
【为了阅读性,不要简写】
typedef简化复杂的数据类型定义
- 将长类型定义为短数据类型
typedef unsigned long long int itcast;
itcast i1 = 10;
- 简化block定义
typedef 返回值类型 (^新类型名)(参数列表);
typedef int (^NewType)(); //将返回值是int,无参数的block类型,定义为了NewType
NewType nt = ^int(){return 1 + 2;
};
block访问变量问题
- block代码块可以取定义在外部的变量的值,包括外部的局部变量和全局变量。
- block代码块可以修改全局变量的值,但是不能修改定义在外部的局部变量的值。
- 为局部变量添加
__block
修饰符后,便可在内部操作
block作为函数参数
如何为函数定义block类型的参数?
a.在小括弧中声明1个指定格式的block变量
b.可以使用typedef简化定义如何调用带block参数的函数呢?
a.待调用函数的参数是block类型的,则在调用的时候传入1个形参block要求的代码段。
#import <Foundation/Foundation.h>
//简化block定义
typedef void (^NewBlock)();
//定义函数
void test(NewBlock nb){NSLog(@"~~~~~~~");nb();NSLog(@"~~~~~~~");
}int main(int argc, const char * argv[]) {//创建block对象并初始化NewBlock nb = ^void(){NSLog(@"gogogogogo~");};//调用函数方式一,先创建对象,传对象test(nb);//调用函数方式二,直接传入代码段test(^void(){NSLog(@"gogogogogo~");});return 0;
}
block作为参数的应用案例一
课程152P,对字符串数组进行排序
#import <Foundation/Foundation.h>typedef BOOL (^NewType)(char *country1, char *country2);@interface ArraySort : NSObject
#pragma mark - 冒泡排序,写死的
- (void)sortWithCountry:(char *[])countries andLength:(int)len;#pragma mark - 写代码排序
- (void)sortWithCountry:(char *[])countries andLength:(int)len andCompareBlock:(NewType)compareBlock;@end
#import "ArraySort.h"
#import <string.h>@implementation ArraySort#pragma mark - 冒泡排序,写死的
- (void)sortWithCountry:(char *[])countries andLength:(int)len{
// int len = sizeof(countries)/sizeof(char *);//int len = sizeof(countries); //8for(int i=0; i<len-1; i++){for(int j=0; j<len-1-i; j++){//这里是比较的字母顺序排序,就写死了int res = strcmp(countries[j], countries[j+1]);if(res>0){char *temp = countries[j];countries[j] = countries[j+1];countries[j+1] = temp;}}}
}#pragma mark - 写代码排序
- (void)sortWithCountry:(char *[])countries andLength:(int)len andCompareBlock:(NewType)compareBlock{for(int i=0; i<len-1; i++){for(int j=0; j<len-1-i; j++){//让调用者自己写代码完成比较,这里就要执行调用者写的代码来比较j 和 j+1的大小//BOOL (^compareBlock)(char *country1, char *country2);BOOL res = compareBlock(countries[j], countries[j+1]);if(res==YES){char *temp = countries[j];countries[j] = countries[j+1];countries[j+1] = temp;}}}
}
@end
#import <Foundation/Foundation.h>
#import "ArraySort.h"int main(int argc, const char * argv[]) {char *countries[]={"woeo","jfvcpbdpo","vjodoiw"};ArraySort *as = [ArraySort new];
#pragma mark - 冒泡排序
// [as sortWithCountry:countries andLength:sizeof(countries)/8];#pragma mark - 长度排序[as sortWithCountry:countries andLength:sizeof(countries)/8 andCompareBlock:^BOOL(char *country1, char *country2) {int res = (int)strlen(country1)-(int)strlen(country2);if(res>0){return YES;}return NO;}];for (int i=0; i<sizeof(countries)/8; i++) {NSLog(@"%s", countries[i]);}NSLog(@"-------");#pragma mark - 字母顺序排序[as sortWithCountry:countries andLength:sizeof(countries)/8 andCompareBlock:^BOOL(char *country1, char *country2) {int res = strcmp(country1, country2);return res>0;}];
#pragma mark - 遍历数组for (int i=0; i<sizeof(countries)/8; i++) {NSLog(@"%s", countries[i]);}return 0;
}
block作为参数的应用案例二
#import <Foundation/Foundation.h>@interface CZArray : NSObject{int _arr[10];
}- (void)bianLi;- (void)bianLiWithBlock:(void (^)(int val))processBlock;@end
#import "CZArray.h"@implementation CZArray//创建出来就会*10
- (instancetype)init{if(self=[super init]){for(int i=1; i<11; i++){_arr[i] = i*10;}}return self;
}
//遍历打印
- (void)bianLi{for(int i=0; i<10; i++){//这里将元素的处理写死了,想要让调用者自己写代码处理NSLog(@"%d", _arr[i]);}
}
//遍历后自行处理
- (void)bianLiWithBlock:(void (^)(int))processBlock{for(int i=0; i<10; i++){processBlock(_arr[i]);}
}
@end
#import <Foundation/Foundation.h>
#import "CZArray.h"int main(int argc, const char * argv[]) {CZArray *ca = [CZArray new];[ca bianLi];[ca bianLiWithBlock:^(int val) {NSLog(@"val = %d", val+1);}];return 0;
}
block作为方法\函数返回值时,类型一定要用typedef定义【基本不用】
block与函数的异同
相同点:都可以用来封装代码
不同点:
1)block是一个类型,函数是一个函数
2)block可以作为函数的参数、返回值
protocol协议
- 作用
1)声明方法。不能声明属性也不能写方法的实现。
2)一个类遵守某个协议后,便拥有了所有方法的声明,而不用自己去定义。
- 协议的声明
@protocol 协议名称 <NSObject>
方法的声明;
@end
新建1个协议的方式:NewFile OC-File - protocol协议的文件名:只生成1个.h文件。
在协议中,只能写方法的声明。
- 类遵守协议。
类只要遵守1个协议,那么这个类就拥有了这些协议中定义的所有的方法的声明了。
@interface类名:父类名 <协议名称>
@end
:表示继承。
<>表示遵守的协议
如果类不实现协议中的方法,也不会报错,编译器只是会报警告。
但是当创建对象,来调用这个没有实现的协议中的方法的时候,就会报错。
- 协议与继承
类是单继承,但是协议可以多遵守。
@interface 类名:父类名 <协议名称1,协议名称2......> @end
- @required 与@optional
@required修饰的方法声明,在遵守类中必须被实现,否则编译期会发出警告。
@optional修饰的方法声明,在遵守类中可以选择性实现,没有警告。
即使不实现,编译器是不会报错的,仍然可以编译运行。
这两个关键字的主要作用:告诉遵守协议的类哪些方法是必须要实现的,因为这些方法我会调用。默认的是@required
- 协议的继承
协议可以继承另外1个协议,并且可以多继承。
协议之间继承的语法格式
@protocol A协议名称 <B协议名称> @end
代表A协议继承自B协议,A协议中既有自己的方法声明,也有B协议中的方法声明。
NSOBject:这是1个类。是所有的OC类的基类。这个类是苹果早就定义好得.
NSOBject:这也是1个协议。也是苹果早就定义好得,这个协议被NSObject类遵守。
所以,所有的OC对象都拥有这个协议中的所有的方法。这个协议我们也叫做基协议。
写协议的规范:任何1个协议,必须要间接的或者直接的去遵守这个NSObject基协议。协议的名称可以和类的名称相同。
- 协议的类型限制
声明1个指针,这个指针可以指向任意的对象,但是要求指向的对象要遵守指定的协议。如果不遵守最起码要报1个警告。
需要遵守多个协议则用逗号隔开。
NSObject<StudyProtocol> *obj1 = [Studnet new];
id<StudyProtocol> obj2 = [Studnet new];
代理设计模式
Foudation框架
NSString
概述
NSString是保存OC字符串的数据类型。NSString的本质是1个类。
最标准的创建NSString对象的方式:
NSString *str1 =[NSString new];
NSString *str2 = [[NSString alloc] init];
NSString *str3 = [NSString string];
NSString对象就是用来存储字符串。
格式控制符复习:
%p:打印指针变量的值,打印地址
%@:打印指针指向的对象
NSString的恒定性
1)以NSString *s = @"sld";
形式创建的字符串对象存储在常量区(数据段)。
2)标准形式,如NSString *s = [NSString stringWithFormat:@"sld"];
创建的对象存在堆区。堆中的对象也是@"sld"
3)以任何形式创建了字符串对象后,这个字符串的内容就不变了。重新赋值后,是创建了新的字符串对象,拥有了新的地址。
4)创建字符串对象时,系统先检查对应的区域,有相同的对象就直接指过去,没有则创建新的对象。
5)存储在常量区的数据不会被回收,所以常量区的字符串对象也不会被回收。
NSString最最最常用方法
1)拼接创建字符串对象
+ (instancetype)stringWithFormat:(NSString *)format,...
[NSString stringWithFormat:@"大家好,我叫%@,今年%d岁", str, age];
2)获取字符串长度
@property (readonly) NSUInteger length;
str.length
3)得到指定下标的字符
- (unichar)characterAtIndex:(NSUInteger)index;
打印该返回值时,格式控制符用%C
4)判断两字符串内容是否相同
- == 运算符比较的是常量、变量的值,比较的是指针变量的地址值。
- (BOOL)isEqualToString:(NSString *)aString;
比较当前对象和传入字符串的内容是否相同。
4)将C语言字符串转换为OC字符串
+ (instancetype)stringWithUTF8String:
C语言字符串char *str = "jack";
5)将OC语言字符串转换为C字符串
@property (nullable, readonly) __string const char *UTF8String;
NSString *str = @“slwlj”;
const char *s = str.UTF8String;
字符串的读写 IO
把字符串写到磁盘某文件中
- (BOOL)writeToFile: (NSString *)path atomically:(B0OL)useAuxiliaryFile encoding:
(NSStringEncoding)enc error: (NSError **)error;
- 参数1:将字符串内容写入到那1个文件之中 写上文件的路径
- 参数2:YES,先将内容写入到1个临时文件,如果成功再将这个文件搬到指定的目录。安全,效率较低。
NO,直接将内容写入到指定的文件。不安全,效率高。
推荐使用NO - 参数3:指定写入的时候使用的编码,一般写:NSUTF8StringEncoding
使用UTF-8编码。 - 参数4:二级指针,要传递1个NSError指针的地址。
写入成功,这个指针的值就是nil;
写入失败,这个指针就会指向1个错误对象。这个对象描述了发生错误的信息。对象的localizedDescription
方法获取发生错误的简要信息。
所以我们要判断是否写入成功,也可以判断这个指针的值是否为nil。
如果不想知道发生错误的原因,第4个参数给nil就可以了。 - 返回值:BOOL类型的.代表是否写入成功.
读取文件中字符串
+ (nullable instancetype)stringWithContentsOfFile:(NSString *)path encoding:(NSStringEncoding)enc error:(NSError **)error;
参数含义同上
使用URL读写字符串数据
优势:可以读取本地文件、网页文件、FTP服务器上文件
不同的类型的URL地址的写法:
本地磁盘文件:file://Users/itcast/Desktop/1.txt)
网页地址: http://www.itcast.cn/index.htmi
ftp文件的地址: ftp://server.itcast.cn/1.txt//封装地址信息 NSURL *u1 =[NSURL URLWithString:@"http://www.itcast.cn"]; //读取NSURL对象中地址信息包含的数据 NSString *str = [NSString stringwithContentsOfURL:u1 encoding:NSUTF8StringEncoding error:nil]; NSLog(@"%a", str);
Objective-C基础学习笔记相关推荐
- guido正式发布python年份_Python 基础学习笔记.docx
Python 基础学习笔记 基于<Python语言程序设计基础(第2版)> 第一部分 初识Python语言 第1章 程序设计基本方法 1.1 计算机的概念 计算机是根据指令操作数据的设备, ...
- ASP.Net MVC开发基础学习笔记(5):区域、模板页与WebAPI初步
http://blog.jobbole.com/85008/ ASP.Net MVC开发基础学习笔记(5):区域.模板页与WebAPI初步 2015/03/17 · IT技术 · .Net, Asp. ...
- Python3 基础学习笔记 C09【文件和异常】
CSDN 课程推荐:<8小时Python零基础轻松入门>,讲师齐伟,苏州研途教育科技有限公司CTO,苏州大学应用统计专业硕士生指导委员会委员:已出版<跟老齐学Python:轻松入门& ...
- Python3 基础学习笔记 C08 【类】
CSDN 课程推荐:<8小时Python零基础轻松入门>,讲师齐伟,苏州研途教育科技有限公司CTO,苏州大学应用统计专业硕士生指导委员会委员:已出版<跟老齐学Python:轻松入门& ...
- Python3 基础学习笔记 C07【函数】
CSDN 课程推荐:<8小时Python零基础轻松入门>,讲师齐伟,苏州研途教育科技有限公司CTO,苏州大学应用统计专业硕士生指导委员会委员:已出版<跟老齐学Python:轻松入门& ...
- Python3 基础学习笔记 C06【用户输入和 while 循环】
CSDN 课程推荐:<8小时Python零基础轻松入门>,讲师齐伟,苏州研途教育科技有限公司CTO,苏州大学应用统计专业硕士生指导委员会委员:已出版<跟老齐学Python:轻松入门& ...
- Python3 基础学习笔记 C05【字典】
CSDN 课程推荐:<8小时Python零基础轻松入门>,讲师齐伟,苏州研途教育科技有限公司CTO,苏州大学应用统计专业硕士生指导委员会委员:已出版<跟老齐学Python:轻松入门& ...
- Python3 基础学习笔记 C04【if 语句】
CSDN 课程推荐:<8小时Python零基础轻松入门>,讲师齐伟,苏州研途教育科技有限公司CTO,苏州大学应用统计专业硕士生指导委员会委员:已出版<跟老齐学Python:轻松入门& ...
- Python3 基础学习笔记 C03【操作列表】
CSDN 课程推荐:<8小时Python零基础轻松入门>,讲师齐伟,苏州研途教育科技有限公司CTO,苏州大学应用统计专业硕士生指导委员会委员:已出版<跟老齐学Python:轻松入门& ...
- Python3 基础学习笔记 C02【列表】
CSDN 课程推荐:<8小时Python零基础轻松入门>,讲师齐伟,苏州研途教育科技有限公司CTO,苏州大学应用统计专业硕士生指导委员会委员:已出版<跟老齐学Python:轻松入门& ...
最新文章
- Yii获取当前url和域名
- 深度学习概述:当你没有方向时的加油站
- python3 console input_Python console.colorize方法代码示例
- 查询oracle数据库adg的模式,Oracle11g ADG配置
- Python规范:提高可读性
- 2009年EI(美国工程索引)收录的中国期刊
- 前端趋势榜:上周最热门的 10 大前端项目 - 210327
- 电气论文实现:通过电力光伏负荷预测讲解seq2seq翻译模型
- mybatis的二级缓存
- 83. 删除排序链表中的重复元素
- spring mvc学习(8):springmvc常用注解代码
- Nodejs是什么?
- 浏览器兼容性问题汇总
- html 有序无序列表
- 奥维中如何关闭gcj02坐标_QGIS入门知识:啥是坐标系
- command对象提供的3个execute方法是_【面试题】面向对象编程篇-01
- 计算机视觉与医疗PPT,图像理解与计算机视觉经典案例.ppt
- 使用osgeo的GDAL获取tiff文件坐标
- timestamp和datetime的区别
- The MVGC Multivariate Granger Causality Matlab初上手记录