zoukankan      html  css  js  c++  java
  • OC:内存管理、dealloc方法、copy知识点

    属性的声明:使⽤@property声明属性


    例如:@property NSString *name;
    相当于@interface中声明了两个⽅法(setter、getter):

    属性的实现:使⽤@synthesize实现属性

    例如:@synthesize name = _name;
    相当于@implementation实现了setter、getter

    Objective-C提供属性的⺫的是为了简化程序员编码
    为属性提供了⼀些关键字⽤以控制setter、getter的实现细节
    这些关键字我们称为属性的属性(attribute)
    ⼀共3⼤类attribute。

    第⼀类:读写性控制(readonly、readwrite、setter、getter)
    readonly,告诉编译器,只声明getter⽅法(⽆setter⽅法)。

    例如:@property(readonly)NSString *name;

    //等价于 - (NSString *)name;
    readwrite,告诉编译器,既声明setter⼜声明getter。

    例如: @property(readwrite)NSString *name;

    //等价于 - (NSString *)name; 

    - (void)setName:(NSString *)name;
    readwrite是读写性控制的默认设置

    第⼆类:原⼦性控制(nonatomic、atomic)
    atomic。setter、getter⽅法在多线程访问下是绝对安全的,即
    setter、getter内部做了多线程访问处理。原⼦性控制的默认设置是
    atomic
    nonatomic。setter、getter⽅法内部不会做多线程访问处理,仅仅是
    普通的setter、getter⽅法

    程序开发过程中,setter、getter处处都在⽤,如果使⽤atomic,需要不断
    的对setter、getter加锁解锁以保证线程访问安全,会很占⽤系统资源,降低
    系统性能。
    通常设置为nonatomic,某些属性需要线程安全的时候,才定义为atomic。
    例如:@property (readwrite,nonatomic)NSString *name;

    //等价于

    - (NSString *)name; 

    - (void)setName:(NSString *)name;

    第三类:语义设置(assign、retain、copy)
    assign。setter、getter内部实现是直接赋值。
    例如:@property(nonatomic,assign)NSString *name;

    - (void)setName:(NSString *)name{

    _name = name;

    }

    - (NSString *)name{

    return _name;

    }

    retain。setter、getter的内部实现会做内存优化。
    例如:@property(nonatomic,retain)NSString *name;
    - (void)setName:(NSString *)name{

    if(_name != name){

    [_name release];

    _name = [name retain];

    }

    }
    - (NSString *)name{

    return [[_name retain]autorelease];

    }

    copy。setter、getter的内部实现也会做内存优化。
    例如:@property(nonatomic,copy)NSString *name;
    - (void)setName:(NSString *)name{

    if(_name != name){

    [_name release];

    _name = [name copy];

    }

    }
    - (NSString *)name{

    return [[_name retain]autorelease];

    }

    如果属性是⾮对象类型(⽐如int,float等)属性的语义设置使⽤
    assign。
    如果属性是对象类型(⽐如

    系统根据引用计数 per.retainCount 来判断何时要回收。引用技术的作用就是告诉对象是否需要被释放,(回收空间)最后一次的 release 就是让引用计数从1 变为 0,发现引用计数为1 对象自己在使用这块内存,所以系统只需要调用dealloc的方法,销毁对象的内存,无需再将引用计数置为0

      per = nil;//per只是一个指针,就像C语言里面的free擦除

            [per name];

            NSLog(@"%@",[per name]);这次就访问不到上面的名字了

    、NSArray等)属性的语义设
    置使⽤retain。
    如果属性是对象类型并且想得到参数的copy,使⽤copy关键字。

    点语法是Objective-C 2.0中定义的语法格式。提供了⼀种便捷的
    属性访问⽅式。

    凡是符合系统默认setter、getter书写格式的⽅法都可以使⽤点语
    法。
    例如:[person1 setName:@”zhangsan”];可以等价写成
    person1.name = @”zhangsan”;。
    NSString *name = [person1 name];可以等价写成

    NSString *name = person1.name;
    属性是⼀对getter、setter⽅法,点语法是属性的另⼀种调⽤格式。

    KVC(Key-Value-Coding),键值编码,是⼀种间接访问实例变量的⽅
    法。
    key:键,⽤于标识实例变量
    vlaue:实例变量对应的值

    setValue:forKey:
    setValue:forKeyPath:
    setValue:forUndefinedKey:
    setValuesForKeysWithDictionary:

    valueForKey:
    valueForKeyPath:
    valueForUndefinedKey:

    当key不存在的时候,会执⾏setValue:forUndefinedKey:
    系统默认实现是抛出⼀个异常

    属性是Objective-C的重要语法,属性是⼀组getter、setter⽅法,内
    部对实例变量进⾏操作。
    点语法是⼀种属性的另外⼀种调⽤格式。
    KVC是⼀种间接访问实例变量的⽅法。

     存的三大问题

            //野指针(已经退房,但是手里还有钥匙)(避免野指针出现 释放之后,将指针设为指向空)就是访问了没有所有权的空间,如果你想使用,必须保留对象的所有权,就是内存还存在

            //过度释放(就是对一块内存多次释放)

            //内存泄露 (开辟了空间,没有回收,苹果给的一个系统的最大内存是50M

    OC的内存管理机制:GC(垃圾回收机制)、引用计数机制。对于我们的IOS的内存管理方式我们用的是:引用计数机制。引用计数机制又分为:ARC(自动引用计数):自动管理内存,空间的开辟需要我们通过代码去完成,(程序员用打吗开辟空间)但是空间的回收是由我们的系统区回收(他回收空间的机制还是基于MRC)这是系统默认的内存管理机制

    MRC (手动引用计数):手动管理内存,同样他是由开发人员开辟空间,不同的是,他的回收是由程序员自己负责来回收,(程序员开辟,程序员回收)所谓的回收就是开辟的对象在使用完之后要及时的释放内存。比起 ARC 可以使我们更加灵活的去控制我们的内存的使用以及何时去释放。

    先学习MRC,理解之后,再学习ARC。

    使用对象的前提是,一个对象(我家的狗)存在,[dog alloc]牵着狗,其他人也想遛狗,引用计数机制

    影响引用计数的几个方法:

             alloc 对象在堆区开辟空间,空间由无到有的过程,引用计数器由 0 ---- 1

             retain 将原来的对象的引用计数器 + 1

             copy   拷贝一份新的对象,将新的对象的引用计数器 + 1;原来的引用计数器不变

             (深 copy 拷贝之后是重新开辟的空间)

             release 将原有的对象的引用计数器 - 1(立即 - 1

             autorelease 延缓 - 1,在未来的某个时候使用对象的引用计数器 - 1,(就是离他最近的一个释放池里释放)

     例如:

     Person * per = [Person alloc];//per引用计数器 0 1

           per.name = @"zahngsan";

            NSLog(@"%lu",per.retainCount);//retainCount 是无符号长整型,ARC不可以用,记录当前对象的引用计数

           [per release];//释放

            NSLog(@"%lu",per.retainCount);//为什么在这里还是1呢?     因为为1的时候有关延迟 

    在Person.m文件里重写方法

    //重写 dealloc 方法 (销毁方法,当对象的引用计数器为0的时候,则会自动调用这个方法,来回收该空间)

    -(void)dealloc{

        NSLog(@"%@ oh,no 我被释放掉了",self);//self就是当前被释放掉的对象

        [super dealloc];//调用父类的方法,才算真正的把内存销毁掉了

    }

    然后运行,看上面的例子,打印的结果。这时候 returnCount仍然是1,为什么?因为指针还在,但是已经是个野指针了

    系统根据引用计数 per.retainCount 来判断何时要回收。引用技术的作用就是告诉对象是否需要被释放,(回收空间)最后一次的 release 就是让引用计数从1 变为 0,发现引用计数为1 对象自己在使用这块内存,所以系统只需要调用dealloc的方法,销毁对象的内存,无需再将引用计数置为0

             会发现打印的结果还是1,那到底这个对象销毁了没有?

             判断对象的内存是否回收,只需要重写一下,对象销毁的方法,dealloc方法,如果走了这个方法,这就说明对象已经被销毁,说明该空间已经被回收

            NSLog(@"%p",per);//这里每次打印的地址都不一样,说明已经是个野指针了

            per = nil;//per只是一个指针,就像C语言里面的free擦除(这里就是上面写的“野指针”的那种情况)

            [per name];

            NSLog(@"%@",[per name]);

    总结:这里一定要弄清楚

    代码

            //创建两个Person

            Person * perr = [[Person alloc]init];

            Person * per1 = [[Person alloc]init];// 0 -> 1

            Person * per2 = [[Person alloc]init];

            per2 = [perr copy];//这里崩溃的原因是:Person这个类没有遵循NSCopying协议,要想让他不崩溃,首先要让Person遵循NSCopying协议 必须实现 copyWithZone方法(注意看正确的写法)

            //打印的数字很大,所以不能仅仅依靠 retainCount 判断

            NSLog(@"%lu",per2.retainCount);

            //allock retain  会让对象引用计数器+1 copy 新的对象引用计数器 + 1

            [per1 retain];//1 -> 2

            NSLog(@"per1的引用计数器%lu",per1.retainCount);

            [perr release];

            [per1 release];

            [per1 release];//这一次销毁对象的内存

            [per2 release];

            [per2 release];//这一次销毁对象的内存

            per1 = nil;

            per2 = nil;

            //内存管理的原则(retainCount是辅助我们来查看对象引用的次数)

            /*

             内存管理原则

             如果对一个对象进行 alloc retain copy 之后,你就拥有对这个对象的所有权,同时,我们就需要负责对这个对象进行 release 或者 autorelease 

             只要你调用了这三个方法(alloc retain copy ),下面就必须进行 release 或者 autorelease

             */

           

            //字符串类对象(有点比较特殊)

            NSString * str = [[NSString alloc]initWithFormat:@"abcdefghip"];//如果字符创串长度只有“xyz”的话,就会打印的结果也是比较长,如果小于10个字母就会打印很长的(retainCount)结果,如果大于等于10个字母 就会正常,因为小于10个字母的时候,有可能字符串对象不是放在堆区的内存,而是根据对应字符串所占空间大小可能分配在常量区,可能分配在堆区。。

            //如果对象分配在常量区的话,他的 retainCount 就是一串非常大的数字(这个时候是以 %lu 打印,就是很大的数字,如果以 %ld 打印的话,输出的就是 - 1

            NSLog(@"%lu",str.retainCount);//

            NSString * str1 = [[NSString alloc]initWithFormat:@"abc"];

            NSLog(@"%ld",str1.retainCount);//显示 -1

            //不要以retainCount为主要参考,还是以上面的内存管理原则为主 //结果说明他是在常量区的字符串

     

            [str release];

            [str1 release];//此时的str1的空间已经被回收,下面不能在使用str,否则会成为野指针

            str  = nil;//这个一般放到dealloc方法里面写

            str1 = nil;

            NSString * str2 = @"niujian";

            NSLog(@"%lu",str2.retainCount);

    //        [str2 release];//写法错误。因为不是在堆区的,不能去人为的操作释放(这里以上面的原则为标准)

    //        str2 = nil;    //写法错误。

            //什么时候才能满足引用计数的概念?

            //1.必须是一个对象  2.对象必须在堆区空间上

            //注意:如果对象是在其他区的话,打印出的就是一个非常大的数字(%lu)。或者是 -1%ld

            

            //练习:

            Person * per3 = [[Person alloc]init];//0---1

            NSLog(@">>%lu",per3.retainCount);

            Person * per4 = [[Person alloc]init];//0---1

            [per3 retain];//1---2

            NSLog(@">>%lu",per3.retainCount);

            [per3 retain];//2---3

            NSLog(@">>%lu",per3.retainCount);

            [per3 release];//3---2

            NSLog(@">>%lu",per3.retainCount);

            [per3 autorelease];//这个会在某个时刻(就看距他最近的自动释放池)2---2

            NSLog(@">>%lu",per3.retainCount);

            [per3 release];//2---1//上一次还有个autorelease就会自动的减去1

            NSLog(@">>%lu",per3.retainCount);

            [per4 release];//虽然是1但是已经被释放掉了

            NSLog(@">>%lu",per4.retainCount);//

            //[per3 release];//过度释放,会导致 crash

            per3.name = @"liucuihua";//重写的方法里面已经把当前的对象的指针置为nil

            per3.age = 22;

            NSLog(@"%@ %lu",per3.name,per3.age);

         


    autorelease:会将对象放到离他最近的自动释放池中,挡自动释放池销毁的时候,对应的池中的每一个调用autorelease的对象发送release消息,让对象的引用计数器减1

      #pragma waring: 在 MRC 下只要你调用了 alloc , retain ,copy 就必须进行 release 或者 autorelease

     在定义属性的时候,属性自动生成的 _属性名  默认是私有的

    assign语义机制下,不需要引用计数器,因为对于该语义类型的属性是直接赋值操作,取值操作

    对于属性的属性是copy 或者 retain 的话我们的写法:

    @synthesize name = _name;

    -(void)setName:(NSString *)name{

        //要把系统的内存机制改为手动管理

        if (_name != name) {

            [_name release];

            _name = [[name copy]autorelease];

        }

    }

    -(NSString *)name{

        return [[_name copy]autorelease];//如果前面的属性的属性是retain那么这里的copy 要换成 retain

    }

     注意: peo1 = [peo retain];//这样的写法是错误的,这时候指针重新指向了peo1,就是peo1指向了peo对象(peo对象的引用计数器加了1

    代码:

    //
    //  main.m
    //
    #import <Foundation/Foundation.h>
    #import "Person.h"
    
    int main(int argc, const char * argv[]) {
        @autoreleasepool {
            // insert code here...
            NSLog(@"Hello, World!");
            //内存的三大问题
            //野指针(已经退房,但是手里还有钥匙)(避免野指针出现 释放之后,将指针设为指向空)就是访问了没有所有权的空间,如果你想使用,必须保留对象的所有权,就是内存还存在
            //过度释放(就是对一块内存多次释放)
            //内存泄露 (开辟了空间,没有回收,苹果给的一个系统的最大内存是50M)
            //oc里面的内存管理方式(1)GC垃圾回收 (2)引用计数(作为IOS只会用到的)ARC自动管理内存自动管理内存,空间的开辟需要我们通过代码去完成,(程序员用编码开辟空间)但是空间的回收是由我们的系统区回收他回收空间的机制还是基于MRC)这是系统默认的内存管理机制         MRC 手动管理内存手动管理内存,同样他是由开发人员开辟空间,不同的是,他的回收是由程序员自己负责来回收,(程序员开辟,程序员回收)所谓的回收就是开辟的对象在使用完之后要及时的释放内存。比起 ARC 可以使我们更加灵活的去控制我们的空间使用以及何时去释放。
            
            //内存管理
            //内存管理机制是引用计数机制
            /*
             影响引用计数的几个方法:
             alloc 对象在堆区开辟空间,空间由无到有的过程,引用计数器由 0 ---- 1
             retain 将原来的对象的引用计数器 + 1
             copy   拷贝一份新的对象,将新的对象的引用计数器 + 1;原来的引用计数器不变
             (深 copy 拷贝之后是重新开辟的空间)
             release 将原有的对象的引用计数器 - 1(立即 - 1)
             autorelease 延缓 - 1,在未来的某个时候使用对象的引用计数器 - 1,(就是离他最近的一个释放池里释放)
             
             */
            Person * per = [Person alloc];//per引用计数器 由0 到 1
            per.name = @"zahngsan";
            NSLog(@"%lu",per.retainCount);//retainCount 是无符号长整型,(是非自动内存管理)ARC不可以用,记录当前对象的引用计数
            [per retain];//per引用计数器 由1 到 2
    //        NSLog(@"%lu",per.retainCount);
    //        [per release];//释放 系统会自己处理 retainCount 为0 就释放对象(在销毁的内部才变为0)
            NSLog(@"%lu",per.retainCount);//打印还是1,为什么?本来引用对象应该是0才对,为什么打印的结果却是1?
            [per release];
            [per release];
            /*
             系统根据引用计数 per.retainCount 来判断何时要回收。引用技术的作用就是告诉对象是否需要被释放,(回收空间)最后一次的 release 就是让引用计数从1 变为 0,发现引用计数为1, 对象自己在使用这块内存,所以系统只需要调用dealloc的方法,销毁对象的内存,无需再将引用计数置为0
             会发现打印的结果还是1,那到底这个对象销毁了没有?
             判断对象的内存是否回收,只需要重写一下,对象销毁的方法,dealloc方法,如果走了这个方法,这就说明对象已经被销毁,说明该空间已经被回收
             */
            NSLog(@"%p",per);//这里每次打印的地址都不一样,说明已经是个野指针了
            per = nil;//per只是一个指针,就像C语言里面的free擦除 他就不再指向原来的内存地址了
            [per name];
            NSLog(@"%@",[per name]);
    //        [per retain];
    //        //[per copy];
    //        NSLog(@"%lu",per.retainCount);
            
            
            
            
            //下午代码
            //创建两个Person类
            Person * perr = [[Person alloc]init];
            Person * per1 = [[Person alloc]init];// 0 -> 1
            Person * per2 = [[Person alloc]init];
            per2 = [perr copy];//这里崩溃的原因是:Person这个类没有遵循NSCopying协议,要想让他不崩溃,首先要让Person遵循NSCopying协议 必须实现 copyWithZone方法(注意看正确的写法)
            //打印的数字很大,所以不能仅仅依靠 retainCount 判断
            NSLog(@"%lu",per2.retainCount);
            //allock retain  会让对象引用计数器+1 copy 新的对象引用计数器 + 1
            [per1 retain];//1 -> 2
            NSLog(@"per1的引用计数器%lu",per1.retainCount);
            [perr release];
            [per1 release];
            [per1 release];//这一次销毁对象的内存
            [per2 release];
    //        [per2 release];//这一次销毁对象的内存
    //        per1 = nil;
    //        per2 = nil;
            //内存管理的原则(retainCount是辅助我们来查看对象引用的次数)
            /*
             内存管理原则
             如果对一个对象进行 alloc retain copy 之后,你就拥有对这个对象的所有权,同时,我们就需要负责对这个对象进行 release 或者 autorelease 
             只要你调用了这三个方法(alloc retain copy ),下面就必须进行 release 或者 autorelease
             */
            
            
            //字符串类对象(有点比较特殊)
            NSString * str = [[NSString alloc]initWithFormat:@"abcdefghip"];//如果字符创串长度只有“xyz”的话,就会打印的结果也是比较长,如果小于10个字母就会打印很长的(retainCount)结果,如果大于等于10个字母 就会正常,因为小于10个字母的时候,有可能字符串对象不是放在堆区的内存,而是根据对应字符串所占空间大小可能分配在常量区,可能分配在堆区。。
            //如果对象分配在常量区的话,他的 retainCount 就是一串非常大的数字(这个时候是以 %lu 打印,就是很大的数字,如果以 %ld 打印的话,输出的就是 - 1)
            NSLog(@"%lu",str.retainCount);//通过retainCount可以判断他是在哪个区上
            NSString * str1 = [[NSString alloc]initWithFormat:@"abc"];
            NSLog(@"%ld",str1.retainCount);//显示 -1
            //不要以retainCount为主要参考,还是以上面的内存管理原则为主 //结果说明他是在常量区的字符串
    
            [str release];
            [str1 release];//此时的str1的空间已经被回收,下面不能在使用str,否则会成为野指针
            str  = nil;//这个一般放到dealloc方法里面写
            str1 = nil;
            NSString * str2 = @"niujian";
            NSLog(@"%lu",str2.retainCount);
    //        [str2 release];//写法错误。因为不是在堆区的,不能去人为的操作释放(这里以上面的原则为标准)
    //        str2 = nil;    //写法错误。
            //什么时候才能满足引用计数的概念?
            //1.必须是一个对象  2.对象必须在堆区空间上
            //注意:如果对象是在其他区的话,打印出的就是一个非常大的数字(%lu)。或者是 -1(%ld)
            
            //练习:
            Person * per3 = [[Person alloc]init];//0---1
            NSLog(@">>%lu",per3.retainCount);
            Person * per4 = [[Person alloc]init];//0---1
            [per3 retain];//1---2
            NSLog(@">>%lu",per3.retainCount);
            [per3 retain];//2---3
            NSLog(@">>%lu",per3.retainCount);
            [per3 release];//3---2
            NSLog(@">>%lu",per3.retainCount);
            [per3 autorelease];//这个会在某个时刻(就看距他最近的自动释放池)2---2
            NSLog(@">>%lu",per3.retainCount);
            [per3 release];//2---1//上一次还有个autorelease就会自动的减去1
            NSLog(@">>%lu",per3.retainCount);
            [per4 release];//虽然是1但是已经被释放掉了
            NSLog(@">>%lu",per4.retainCount);//
    //        [per3 release];//过度释放,会导致 crash
            per3.name = @"liucuihua";//重写的方法里面已经把当前的对象的指针置为nil了
            per3.age = 22;
            NSLog(@"%@ %lu",per3.name,per3.age);
    //        per3 = nil;
    //        per4 = nil;
    #pragma waring: (MAR管理原则) 只要你调用了 alloc , retain ,copy 就必须进行 release 或者 autorelease
            //autorelease:会将对象放到离他最近的自动释放池中,挡自动释放池销毁的时候,对应的池中的每一个调用autorelease的对象发送release消息,让对象的引用计数器减1
    
            Person * peo =[[Person alloc]init];
            Person * peo1 = [[Person alloc]init];
            peo.name = @"HHH";
            peo.age = 23;
    //        peo1 = [peo retain];//这样的写法是错误的,这时候指针重新指向了peo1,就是peo1指向了peo对象(peo对象的引用计数器加了1)
            peo1 = [peo copy];//把peo 拷贝一份给 peo1
            NSLog(@"%@ %ld",peo1.name,peo1.age);
            
        }
        return 0;
    }
    View Code  main.m文件
    //
    //  Person.h
    
    #import <Foundation/Foundation.h>
    
    @interface Person : NSObject<NSCopying>
    //遵循NSCopying协议,同时间也服从协议中的方法
    @property(nonatomic,copy)NSString *name;//这里生成的 _name 默认是私有的
    @property(nonatomic,assign)NSInteger age;
    //assign(语义机制下)不需要进行引用计数器,他的内部是直接的赋值操作
    -(void)setAge:(NSInteger)age;
    -(NSInteger)age;
    @end
    View Code Person.h文件
    //
    //  Person.m
    
    #import "Person.h"
    
    @implementation Person
    
    -(id)copyWithZone:(NSZone *)zone{
        //    Person * per = [[Person copyWithZone:zone]init];
        //新创建一个对象
        Person * per = [[Person allocWithZone:zone]init];
        //对应吧原对象的内容也赋值给新的对象
        per.name = self.name;
        per.age = self.age;
        //返回新建的对象
        return per;
    }
    
    //@synthesize name ; age;//这种写法就是为多个属性(不要系统默认生成的setter getter方法)
    //@synthesize name = _name;//这里是告诉系统系统不要提供 setter getter 方法
    @synthesize age = _age;//这里写之后,我不要系统提供给我的 setter getter 方法,后面也访问不到 _age(系统也不会给你 _age )表明 setter getter 方法要自己写
    //@synthesize age;//这时候就能够访问到 _age
    @synthesize name = _name;
    -(void)setName:(NSString *)name{
        //要把系统的内存机制改为手动管理
        if (_name != name) {
            [_name release];
            _name = [[name copy]autorelease];
        }
    }
    -(NSString *)name{
        return [[_name copy]autorelease];//如果前面的属性的属性是retain那么这里的copy 要换成 retain
    }
    -(void)setAge:(NSInteger)age{
        //cmd + shift + k (快捷键) 就是把之前的程序的缓存 clean 清除(用于例如,我先手写的实例变量,然后自己又用属性写法,这时候系统不能能识别)
        //属性默认生成的 _属性 是私有的在访问的时候用点语法
        //在用KVC的setvalue: forKey方法的时候,一定要重写 setValue:forUndefinedKey方法
        _age = age;
    }
    -(NSInteger)age{
        return _age;
    }
    //重写 dealloc 方法 (销毁方法,当对象的引用计数器为0的时候,则会自动调用这个方法,来回收该空间)
    -(void)dealloc{
        
        //我们还有一个age属性,他不需要回收,他只用于存值
        NSLog(@"对象为:%@ 。oh,no 我被释放掉了",self.name);//self就是当前被释放掉的对象
        [super dealloc];//调用父类的方法,才算真正的把内存销毁掉了
        self.name = nil;//这里吧要释放掉的对象的指针置为 nil
    }
    @end
    View Code Person.m文件
  • 相关阅读:
    点击区域外隐藏该区域,event.stopPropagation()
    PHP 笔记一(systax/variables/echo/print/Data Type)
    <hr> 的18种样式
    CSS 设置背景透明度,不影响子元素
    console.dir() 与 console.log() 区别
    JS 鼠标滚轮事件(mousewheel/DOMMouseScroll)
    HTML 字符图案
    CSS 样式优先级
    替换元素和不可替换元素,块级元素和行内元素
    CSS3 笔记五(Buttons)
  • 原文地址:https://www.cnblogs.com/benpaobadaniu/p/4746791.html
Copyright © 2011-2022 走看看