面向对象
面向对象的编程思维,简而言之就是:一切皆对象。面向对象的语言区别于C语言(面向过程),C语言是按照代码的执行顺序去实现项目功能,而高级编程语言是从对象的角度去把控,详细解释就是:任何事件都是由事物构成,事物拥有自己特有的属性(对象的固有属性和行为属性),大事件通过各种事物之间的相互作用(行为调用)实现。OC是从C语言的基础之上发展而来的,是C语言的面向对象。OC完全兼容C语言,可以实现OC与C的混合编程,一起生成可执行文件。
OC与C的部分不同点
参考代码:
#import
#import
/*
*要求定义一个人类
*事物名称:人
*属性:年龄(age),身高(height),体重(weight)
*行为:吃饭(eat),睡觉(sleep),散步(walk)
*/
@interface Person : NSObject
{
@public
//属性
int _age;
double _height;
double _weight;
}
//行为
-(void)eat:(char *)food;
-(void)sleep;
-(void)walk;
+(void)about;
@end
@implementation Person
-(void)eat:(char *)food
{
NSLog(@"吃%s",food);
}
-(void)walk
{
NSLog(@"开始遛弯");
}
-(void)sleep
{
NSLog(@"开始睡觉");
}
+(void)about
{
NSLog("Hello");
}
@end
int main(int argc, const char * argv[]) {
//1. 通过类创建对象
/*
*1.开辟存储空间
*2.初始化所有属性
*3.返回指针地址
*/
//因为在堆栈中创建的对象,实质是一个结构体,所以可以通过结构体指针直接访问属性
/*
*创建对象的时候,返回的地址其实就是类的第0个属性的地址
*结构体创建完成以后,结构体的地址其实就是结构体第0个成员的首地址
*但是需要注意的是:类的第0个属性并不是我们编写的_age,而是一个叫做isa的属性
*isa是一个指针,占8个字节
*
*其实类也是一个对象,也就意味着Person也是一个对象
*平时我们所说的创建对象,就是通过一个类对象创建一个新的对象
*类对象是系统自动帮我们创建的,里面保存了当前对象的所有方法,准确说是一个方法列表,包括方法体
*而实际对象是程序自己员手动通过new来创建的,而实例对象中有一个isa指针就指向了创建它的那个类对象
*/
Person *p=[Person new];
p->_age=30;
p->_height=1.75;
p->_weight=65.0;
[p walk];
[Person about];
NSLog(@"p=%p",p);
NSLog(@"&age=%p",&(p->_age));
/*
*结构体创建完成以后,结构体的地址其实就是结构体第0个成员的首地址
*/
// struct Person
// {
// int age;
// char *name;
// };
// struct Person sp;
// NSLog(@"&sp=%p",&sp);
// NSLog(@"&age=%p",&sp.age);
return 0;
}
1.#import和#include的区别
import的功能和include的功能是一样的,都是导入文件,但是OC为了避免C语言中的重复导入问题(避免使用复杂的头文件卫士)为import集成了避免重复导入的机制。下面介绍一下C语言中头文件卫士的写法:
#ifndef _TEST_H
#define _TEST_H//一般是文件名的大写
头文件结尾写上一行:
#endif
这样一个工程文件里同时包含两个test.h时,就不会出现重定义的错误了。
分析:当第一次包含test.h时,由于没有定义_TEST_H,条件为真,这样就会包含(执行)#ifndef _TEST_H和#endif之间的代码,当第二次包含test.h时前面一次已经定义了_TEST_H,条件为假,#ifndef _TEST_H和#endif之间的代码也就不会再次被包含,这样就避免了重定义了。
当然以上这些内容都是关于C语言-预处理的知识内容,具体细节参考:C语言再学习——C预处理器和头文件为什么要加#ifndef #define #endif
2.OC实现面向对象的原理
OC是对C语言进行了面向对象的封装,即通过C语言实现面向对象,那么是如何实现的?其实是结构体+指针的方式实现的。OC中的对象在内存中其实是以结构体的形式进行存储的。这一点的内容就涉及到了runtime运行时的内容。在这里由于目前还没有涉及到运行时内容,所以对本部分内容进行了适当的简化,同时也增进了后期运行时内容的进一步学习与理解。
OC中的对象是通过结构体来实现的,即在堆中对象的存储数据结构是struct,创建的对象变量,其实是一个指向相应类结构体的指针。由于对象的实际结构是结构体,那么我们可以通过"->”这样的方式去访问实例对象中的属性,代码如下:
IPhone *p=[IPhone new];
p->_color=0;//默认@interface中的属性都是@protected的,如果想这样子直接访问,需要使用@public
p->_model=0;
在实际的实例对象存储过程中,实例对象内部是不存在实例方法的,只包含属性信息和isa指针信息,isa指针指向该类的类对象,类对象是在程序编译阶段,系统自动创建的,包含了类的所有信息(属性列表、方法列表、类名等信息),是创建实例对象的依据(模板)。需要注意的是:结构体创建完成以后,结构体的地址其实就是结构体第0个成员的首地址,但是类对象的首地址是isa指针属性的存储值,isa是一个指针,占8位。具体在内存中的存在方式如下图所示:
所以从上图中我们可以联想到,[Person new]方法,其实是调用了Person类对象的new方法。参考代码中的[p walk]和 [Person about]对比会发现,类方法的执行效率高于实例方法。图解如下:
两幅图的分析,故意将元类的概念略去,因为类方法实际是存储在元类中的,但是在此为了方便理解,同时也为后期进阶做准备。
上图中涉及到了堆、栈、代码区等内容,具体可以参考iOS中的内存管理和 C语言中内存分配和linux内存管理和深入浅出-iOS内存分配与分区
3.OC中的类方法和实例方法以及函数
OC和C中的函数的对比,其实很简单,在这里直接上代码,我觉得对于身为程序员的自己来说,可能会更加清晰,注意点:在Java中对象可以直接调用类方法,但是在OC中对象不能调用类方法,必须通过类调用类方法。静态方法、类方法和对象方法的区别
//
// main.m
// OC_day1_04_类方法
//
// Created by 刘旭辉 on 2017/10/13.
// Copyright © 2017年 刘旭辉. All rights reserved.
//
#import
typedef enum
{
KIColorBlace,//黑色
KIColorWhite,//白色
KIColorTuHaoJin//土豪金
}IColor;
//1.编写类的声明
@interface IPhone : NSObject
{
@public
double _size;
// int _color; //颜色 0代表黑色 1代表白色 2代表土豪金
int _cpu;
int _model;
IColor _color;
}
/*
*行为
*OC中的行为和C语言中的函数一样,都是用来保存一段特定功能的代码
*OC中定义一个方法,也分为声明和实现,声明在@interface中,实现写在@implementation
*
*C语言中的函数分为两种:内部函数和外部函数
*OC中的函数也分为两种:类方法和对象方法
*类方法只能用类名调用,对象方法只能用对象调用
*OC中的类方法用+表示,OC中的对象方法用-表示
*
*编写C语言函数的规律:1.确定函数名称;2.确定形参;3.确定返回值;4.确定返回值类型
*编写OC方法也有规律,规律和C语言一模一样
*/
/*C语言方法
void about();
void about()
{
printf("model=cpu=size=color");
}
*/
//注意:OC中的方法,如果没有形参不需要写(),而是直接写一个;
//为什么OC中没有形参不需要写()呢?因为OC方法中的()有特殊用途,OC方法中的()是用来括住数据类型的
//行为的声明
-(void)about;
/*
*C语言中的函数:1.没有返回值没有参数的函数;2.有返回值没有参数的;3.有返回值有参数的;4.没有返回值有参数的
*OC中的方法也有四种
*/
//有返回值没有参数的。例如:读取短信
-(char *)loadMessage;
//有返回值有参数的,例如:打电话
//int signal(int number); C语言中的函数
//注意:OC中的方法如果有参数,那么每个参数的数据类型前面必须加一个:
//注意:当前的有参数的方法的方法名称是 signal: 冒号也是方法名称的一部分
-(int)signal:(int)number;
//有返回值,并且有多个参数的,发短信
//int sendMessage(int number,char* content);
//为了提高阅读性,OC方法允许我们给每一个参数添加一个标签来说明当前参数的含义
-(int)sendMessageWithNumber:(int)number andContent:(char*) content;
//-(int)sendMessage:(int)number :(char*) content; //当前的方法名称叫做:sendMessage::
//没有返回值,有参数的函数
-(void)callWithNumber:(int) number;
//计算器功能(类方法)
//如果你不想每次使用方法都需要创建对象并且开辟存储空间
//并且如果该方法中没有使用到属性(成员变量),那么你可以把这个方法定义为类方法
//对象方法用对象调用 类方法用类名调用
//-(int)sumWithNumber1:(int)number1 andNumber2:(int) number2;
//只需要将对象方法的-号转换成+,那么就定义了一个类方法
//注意:如果实现了一个类方法,就必须去实现类方法
//如果实现的是一个对象方法,那么就必须去实现对象方法
/*
*类方法和对象方法的区别
*0.对象方法以-开头;类方法以+开头
*1.对象方法必须用对象调用,类方法必须用类调用
*2.对象方法中可以直接访问属性(成员变量),类方法中不可以直接访问属性(成员变量)
*3.类方法的优点,调用类方法的效率会比调用对象方法高
*4.类方法和对象方法可以进行相互调用
*4.1对象方法中可以直接调用类方法
*4.2可以在类方法中间接调用对象方法
*4.3在类方法中可以直接调用其他方法
*4.4对象方法中可以直接调用对象方法
*/
/*
*类方法的应用场景
*如果方法中没有使用到属性(成员变量),那么能用类方法就用类方法
*类方法的执行效率比对象方法高
*
*类方法一般用于定义工具方法
*字符串查找
*文件操作
*数据库操作
*/
+(int)sumWithNumber1:(int)number1 andNumber2:(int) number2;
@end
//2.编写类的实现
@implementation IPhone
//行为的实现
-(void)about
{
// NSLog(@"打印本机信息");
//如果在对象方法中想访问该对象的属性,可以直接写上_属性名称即可
[IPhone sumWithNumber1:50 andNumber2:50];
NSLog(@"size=%f,color=%i,model=%i,cpu=%i",_size,_color,_model,_cpu);
}
-(char *)loadMessage
{
return "老婆我们家我做主";
}
-(int)signal:(int)number
{
NSLog(@"打电话给%i",number);
return 1;
}
-(int)sendMessage:(int)number :(char *)content
{
NSLog(@"发短信给%i,内容是%s",number,content);
return 0;
}
-(int)sendMessageWithNumber:(int)number andContent:(char *)content
{
NSLog(@"发短信给%i,内容是%s",number,content);
return 0;
}
-(void)callWithNumber:(int)number
{
NSLog(@"打电话给%i",number);
}
+(int)sumWithNumber1:(int)number1 andNumber2:(int)number2
{
IPhone *p1=[IPhone new];
[p1 about];//在类方法中,可以简介调用对象方法,但是在企业开发中,不建议这样子使用
return number1+number2;
}
@end
int main(int argc, const char * argv[]) {
//1.通过类创建对象
IPhone *p=[IPhone new];
p->_color=KIColorBlace;
p->_model=4;
p->_cpu=1;
p->_size=3.5;
//3.获取对象的属性
// NSLog(@"size=%f,color=%i,model=%i,cpu=%i",p->_size,p->_color,p->_model,p->_cpu);
//4.如果给对象发消息(如果调用对象的方法)
[p about];
//注意:OC中的NSLog对C语言中的字符串支持不是很好,如果返回的是中文的C语言字符串可能输出是乱码,也可能什么都不输出
NSLog(@"%s",[p loadMessage]);
[IPhone sumWithNumber1:1 andNumber2:2];
// [p sendMessage:110 :"help,help"];
[p sendMessageWithNumber:110 andContent:"help"];
[p sendMessage:110 :"help,help"];
[p signal:110];
[p callWithNumber:119];
return 0;
}
void test()
{
//1.创建一个对象
IPhone *p1=[IPhone new];
//2.利用对象调用假发运算方法
// [p1 sumWithNumber1:1 andNumber2:2];
}
4.OC成员变量、局部变量、全局变量的区别以及内存分析
#import
@interface Person : NSObject
{
//写在类声明的大括号中的变量,我们称之为成员变量(或者属性,实例变量)
//注意:
//1.成员变量不能离开类,离开类以后就不是成员变量;
//2.成员变量不能在定义的同时进行初始化;
//3.成员变量只能通过对象来访问
//存储在:堆(当前对应的堆的内存空间中)
//存储在堆中的数据,不会被自动释放,只能程序员手动释放
int age;
}
@end
@implementation Person
@end
//写在函数和大括号外部的变量,我们称之为全局变量
//作用域:从定义的那一行开始,一直到文件末尾
//全局变量可以先定义再初始化,也可以定义的同时初始化
//存储在:静态区
//程序一启动就会分配内存空间,知道程序结束才会释放
int a;
int b=10;
int main(int argc, const char * argv[]) {
//写在函数或者代码块中的变量,我们称之为局部变量
//作用域:从定义的哪一行开始,一直到遇到大括号或者return
//局部变量可以先定义再初始化,也可以定义的同时初始化
//存储在栈中
//存储在栈中的数据有一个特点,系统会自动给我们释放
int num=10;
return 0;
}
#import
@interface Person : NSObject
{
//写在类声明的大括号中的变量,我们称之为成员变量(或者属性,实例变量)
//注意:
//1.成员变量不能离开类,离开类以后就不是成员变量;
//2.成员变量不能在定义的同时进行初始化;
//3.成员变量只能通过对象来访问
//存储在:堆(当前对应的堆的内存空间中)
//存储在堆中的数据,不会被自动释放,只能程序员手动释放
int age;
}
@end
@implementation Person
@end
//写在函数和大括号外部的变量,我们称之为全局变量
//作用域:从定义的那一行开始,一直到文件末尾
//全局变量可以先定义再初始化,也可以定义的同时初始化
//存储在:静态区
//程序一启动就会分配内存空间,知道程序结束才会释放
int a;
int b=10;
int main(int argc, const char * argv[]) {
//写在函数或者代码块中的变量,我们称之为局部变量
//作用域:从定义的哪一行开始,一直到遇到大括号或者return
//局部变量可以先定义再初始化,也可以定义的同时初始化
//存储在栈中
//存储在栈中的数据有一个特点,系统会自动给我们释放
int num=10;
return 0;
}
在以上的代码中已经对成员变量
、全局变量
、局部变量
做了详细的区分,但是在内存中具体是如何存储的?还没有一个明确的概念。通过阅读一些博客,对物理存储有了一定的了解,分析如下:
-
RAM和ROM区分
我们所说的内存,其实指的就是RAM,在程序运行期间系统将ROM中的源代码加载到RAM中,我们所说的栈、堆、全局区/静态区、常量区、代码区其实都是位于RAM中。RAM:运行内存,不能掉电存储。
ROM:存储性内存,可以掉电存储,例如内存卡、Flash。
由于RAM类型不具备掉电存储能力(即一掉电数据消失),所以app程序一般存放于ROM中。RAM的访问速度要远高于ROM,价格也要高。App程序启动,系统会把开启的那个App程序从Flash或ROM里面拷贝到内存(RAM),然后从内存里面执行代码。另一个原因是CPU不能直接从内存卡里面读取指令(需要Flash驱动等等)。 RAM中五大分区详解
- 栈区(stack):
存放的局部变量、先进后出、一旦出了作用域就会被销毁;函数跳转地址,现场保护等;
程序猿不需要管理栈区变量的内存。主要负责函数模块内申请,函数结束时自动释放,存放局部变量,函数变量
~栈区地址从高到低分配;- 堆区(heap):
通过malloc函数或new函数等操作符操作的得到,需要程序员自己管理
ARC的内存的管理,是编译器再便宜的时候自动添加 retain、release、autorelease;
~堆区的地址是从低到高分配- 全局区/静态区(static):
包括两个部分:未初始化过(.bss段区域) 、初始化过(data段区域),即全局区/静态区在内存中是放在一起的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。- 常量区:存放常量字符串;
- 代码区: 存放编写的源代码,由系统自动加载进入;
- 注意:也有人将全局区/静态区(static)和常量区联合在一起成为数据区。
具体图示如下图所示:
5.函数与方法的区别
首先需要理解的一点是:方法是对象的行为,函数是整个文件的一个功能块。方法只能存在于类内部,函数可以存在于整个文件的任何地方。具体代码如下:
#import
/*
*函数和方法的区别
*1.函数属于整个文件,方法属于某一个类,方法如果离开类就不行
*2.函数可以直接调用,方法必须用对象或者类来调用
*注意:虽然函数属于整个文件,但是如果把函数写在类的声明中就会不识别
*3.不能把函数当做方法调用,也不能将方法当做函数调用
*
*
*方法的注意点:
*方法可以只有声明没有实现,也可以只有实现没有声明,编译不会报错,但是运行会报错
*如果方法只有声明而没有实现,则会报错:1.reason:'+[Person demo]:unrecognized selector sent to class 0x100001140',发送了一个不能识别的消息,在Person类中没有+开头的方法
*reason:'-[Person test]:unrecognized selector sent to instance 0x100001140'
*类也可以只有实现没有声明,例如:@implementation Person : NSObjec ... @end
*/
@interface Person : NSObject
//对象方法的声明
-(void)test;
//类方法声明
+(void)demo;
@end
@implementation Person
//对象方法的实现
-(void)test
{
NSLog(@"test");
}
//类方法的实现
+(void)demo
{
NSLog(@"demo");
}
@end
//外部函数的声明
extern void sum();
//内部函数的声明
static void minus();
//外部函数
extern void sum()
{
printf("sum");
}
//内部函数
static void minus()
{
printf("minus");
}
int main(int argc, const char * argv[]) {
return 0;
}
6.结构体作为对象属性的细节
当结构体作为函数(C语言中的称呼)参数或者作为对象属性的情况下,结构体是以拷贝结构体属性的方式进行传递的,此过程传递的不是指针。
结构体作为属性的代码示例:
类模型
#import
/*
*合理的设计一个“学生”类
*学生有*姓名*生日另个属性和说出自己姓名生日的方法
*要求利用设计的学生类创建学生对象,并且说出自己的姓名和年龄
*/
//定义一个新类型Date
typedef struct
{
int year;
int month;
int day;
}Date;
@interface Student : NSObject
{
@public
NSString *_name;
Date _birthday;
}
-(void)say;
@end
@implementation Student
-(void)say
{
NSLog(@"name=%@;year=%i,month=%i,day=%i",_name,_birthday.year,_birthday.month,_birthday.day);
}
@end
结构体属性修改范例
int main(int argc, const char * argv[]) {
//1.创建学生对象
Student *student=[Student new];
[student say];
//2.设置学生对象的属性
student->_name=@"lxh";
/*
*student->_birthday={1992,12,23};错误原因
*1.结构体只能在定义的时候初始化
*2.系统并不清楚它({1992,12,23})是数组还是结构体
*/
student->_birthday=(Date){1992,12,23};
//student->_birthday=(Date){1992,12,23};这句话的本质其实是:结构体拷贝,将结构体{1992,12,23}强制拷贝给了_birthday
/*
Date d1={1999,1,5};
Date d2;
d2=d1; //当结构体作为函数的参数的时候,赋值其实是将结构体内部的属性,进行了一次拷贝
//这里是将d1内部的属性都拷贝了一份赋值给了d2
d2.year=2000;//修改d2的值并不影响d1的值
printf("d1=year=%i\n",d1.year);
printf("d2=year=%i\n",d2.year);
*/
/*
*这种方式赋值也可以,单个赋值
*/
student->_birthday.year=1992;
student->_birthday.month=12;
student->_birthday.day=23;
/*
*第三种赋值方式
*/
Date d={1992,12,23};
student->_birthday=d;
return 0;
}
欢迎关注我的个人微信公众号,免费送计算机各种最新视频资源!你想象不到的精彩!