《iOS知识点梳理-C语言》

知识点总结中,如有幸被您观看,更有可能看到不足,期待指出交流

前言

C语言,开发的基础功底,iOS很多高级应用都要和C语言打交道.所以,C语言在iOS开发中的重要性是很厉害的.现在过来回顾这方面的问题.

查看下面的代码会发生什么
least = MIN(*p++, b)

结果:((* p++) <= b) ? (* p++) :b),这个会产生副作用,指针p会做俩次++的自增操作

用预处理指令#define声明一个常数,用来表达一年中有多少秒(忽略闰年)
define SECONDS_PER_YEAR (60 * 60 * 24 * 365)UL(UL无符号长整型)
写一个标准宏 MIN ,这个宏输入俩个参数并返回较小的一个
define MIN(A,B) ((A) < (B) ? (A): (B))
给出以下代码的输出
int array[5] = {1,2,3,4,5};
int *p = &array[0];
int max = Max(*p++, 1);
printf("%d %d", max, *p);

答案:输出的答案,1,2.
解析:看到宏的时候,就会想到宏的副作用,对于++、--,在宏的使用中会产生一系列的副作用,因此要慎用.下面是其宏的结构

#define Max(X,Y) ((X)>(Y)?(X):(Y))
(*p++) > (1) ? (*p++) :(1)

先比较(1)>(1)>(p++):(1),然后p++了,这个时候p已经指向了2,max为1,*p为2

define定义的宏和const定义的常量有什么区别
  • .#define定义宏的指令,程序在预处理阶段将#define所定义的内容只是进行了替换.所以在程序运行的时候,常量表中并没有#define所定义的宏,系统不为他分配内存.
  • .#define定义的宏指令,编译的时候不会检查数据类型,出错的概率会更大
  • const定义的常量, 在程序运行的时候是存放在常量表中的,系统会给它分配内存,编译的时候会进行检查
    注意: .#define定义表达式时要注意边缘效应例如
#define N 2 + 3 // 我们预想的值是5, 
int a = N / 2; // 结果就是 2 + 3 / 2 = 2.5,
关键字volatile有什么含义,举出三个不同例子

优化器在用到这个变量时必须每次都要小心的重新读取这个变量的值,而不是使用保存在寄存器里面的备份.下面是例子

  • 并行设备的硬件寄存器(如:状态气纯器)
  • 一个终端服务字程勋中会访问到的非自动变量(Non-automatic variables)
  • 多线程应用中呗几个任务共享的变量
完成字符拷贝可以使用sprintf、strcpy、以及memcpy函数,这些有什么区别

这些函数的区别在于实现功能以及操作的对象不同

  • strcpy: 函数操作的对象是字符串,完成从源字符串到目的字符串的拷贝功能.
  • sprintf:这个函数主要用来实现(字符串或基本数据类型)向字符串的转换功能.如果元对象是字符串,并且制定%s格式符,也可实现字符串拷贝功能.
  • memcpy: 函数顾名思义就是内存拷贝,实现建一个内存块的内容复制到另一个内存块这个功能.类存款由对象的其实地址和内存长度信息,并且对象具有可操作性即可.鉴于memcpy函数的长拷贝的特点以及数据类型代表的物理意义,memcpy函数通常限于同种类型数据或对象之间的拷贝,其中当然也包括字符串拷贝以及基本数据类型的拷贝.
    对于拷贝字符串,上述的三个函数都是可以实现的但是实现的效率和使用的方便程度不一样
  • strcpy 最合适的选择,效率高切调用方便
  • snprintf 要额外指定格式符并且进行格式转换,麻烦而且不高效
  • memcpy 虽然高效, 但是需要额外提供拷贝的内存长度这一个参数,易错而且使用不方便.并且长度过道,还会带来性能的下降.
  • 对于非字符串的赋值strcpy和 snprintf 就不行了.这个时候就只能用 memcpy来拷贝了
    static关键字的作用
  • 隐藏,编译多个文件时,所有未加static前缀的全局变量和函数都全局可见
  • 保持变量内容的持久.全局变量和static变量都纯纯在静态 存储区,程序开始运行就初始化,只初始化一次,static控制变量的作用范围.
  • 默认初始化为0,在静态数据区,内存中的所有直接都是0x00,全局变量和static变量都是默认初始化0

static的特别

  • static全局变量和普通的全局变量有什么区别:static全局变量只初始化一次,防止在其他文件单元中被应用
  • static局部比啊年和普通局部变量有什么区别:static局部变量只被初始化一次,洗一次依据上一次的结果值,
  • static函数和普通函数有什么区别:static函数在内存中只有一份,普通函数在每个被调用中维持一份拷贝
    想看的具体一点可以看下面的文章
    Static关键字理解(iOS)
    关键字const
  • const int a; int const a; 作用一样, a是一个长整型数
  • const int a; int const a;a是一个纸箱长整数的指针(整型数是不可修改的,但是指针可以)
  • int * const a; a 是一个纸箱整型数的常指针(真正指向的整型数是可以修改的,但指针是不可以修改的)
  • int const *const a; a 是一个纸箱常整数的常指针(指针指向的整型数是不可修改的, 同事指针也是不可修改的)
    Const 关键字理解(iOS)
堆栈
  • 管理方式:对于栈来说,是有编译器自动管理,无需我们手工控制;对于堆来说,释放工作是有程序员控制,容易产生内存泄漏.
  • 申请大小:
    -- 栈:在windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域.这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在windows下,栈的大小是2M(也有的说是1M,总之是编译时就确定的参数),如果申请的空间超过栈剩余空间时,就会提示 overflow.因此能从栈获得的空间小
    -- 堆:堆是向高地址扩展的数据结构,是不连续的内存区域.这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,二链表的遍历方向是由低地址向高地址.堆的大小受限于计算机系统中有效的虚拟内存.由此可见,堆获取的空间比较活,也比较大.
  • 碎片问题:对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率低下,对于栈来讲,就不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一块内存块从栈中弹出
  • 分配方式:堆都是动态分配,没有静态分配的堆.栈有俩种分配方式:静态分配和动态分配.静态分配是编译器完成的,比如局部变量的分配.动态分配由alloc函数进行分配,但是栈的动态分配和堆是不同的,他的动态分配是由编辑器进行释放,无需我们手工实现.
  • 分配效率:栈是及其系统提供的数据结构,计算机会在底层对栈提供支持:分配专门的寄存器存在放栈的地址,压栈出栈都有专门的指令执行,这就决定了栈的效率比较高.堆则是c/c++函数库提供的,他的机制很复杂的.
数组和指针的区别
  • 数组可以申请在栈区和数据区:指针可以指向任意类型的内存块,sizeof作用于数组时,得到的是数组所占的内存大小,作用于指针时,得到的都是4个字节的大小.
  • 数组名标识数组的首地址,是常量指针,不可修改指向.比如不可以将++作用于数组名上;普通指针的值可以改变,比如可将++作用于指针上
  • 用字符串初始化字符数组是将字符串的内容拷贝到字符数组中;用字符串初始化字符指针是将字符串的首地址赋值给指针,也就是指针指向了该字符串
引用和指针的区别
  • 指针指向一块内存, 内存储存所指向内存的地址
  • 引用是某块内存的别名
  • 引用时不需要解引用(*)而指针需要
  • 引用只在定义时呗初始化,之后不可变,指针可变
  • 引用没有const
  • 引用不能为空
  • sizof引用得到的是所指向变量(对象)的大小,sizeof指针是指针本身的大小
  • 指针和引用的自增(++)运算意义不一样:引用++未引用对象自己++.指针++是真想对象后面的内存.
  • 程序需要尾指针分配内存区域,引用不需要
用变量a给出下面的定义
  • (1)一个整型数
  • (2)一个指向整型数的指针
  • (3)一个指向指针的指针,它指向指针是指向一个整型数
  • (4)一个有10个整型数的数组
  • (5)一个有10个指针的数组,该指针是指向一个整型数的
  • (6)一个指向有10个整型数数组的指针
  • (7)一个指向函数的指针,该函数有一个整型参数并返回一个整型数
  • (8)一个有10个指针的数组,该指针指向一个函数,该函数有一个整型参数并返回一个整数型
(1)int a;
(2)int *a;
(3)int **a;
(4)int a[10]
(5)int *a[10]
(6)int (*)a[10]
(7)int (*)a(int)
(8)int (*a[10])(int)

请写出以下代码输出

int a[5] = {1,2,3,4,5}
int *ptr = (int *)(&a+1);
printf("%d, %d", *(a+1), *(ptr + 1));

参考答案:2,随机值
分析:a代表有5个元素的数组的首地址,a[5]的元素分别是1,2,3,4,5.接下来,a+1表示数据首地址加1,那么就是a[1],对应的值也就是2.但是,这里是&a+1,因为a代表的是整个数组,他的空间大小为5sizeof(int),因此&a+1就是a+5.a是个常量指针,指向当前数组的首地址,指针+1就是移动sizeof(int)个字节.因此,ptr是指向int 类型的指针,而ptr 指向的就是a+5,那么ptr+1也就相当于a + 6,所有最后一个(ptr + 1)就是一个随机值了.而(ptr-1)就相当于a+4,对应的值就是5.

简述内存分区情况
  • 代码区: 存放函数二进制代码
  • 数据区: 系统运行时申请内存并初始化,系统推出时由系统示范,存放全区变量,静态变量,常量
  • 堆区:通过malloc等函数或new等操作浮动动态申请得到,需要程序员手动申请和释放.
  • 栈区:函数模块内申请,函数结束时由系统自动释放,存放局部变量,函数参数
用NSLog函数输出一个浮点类型,结果四舍五入,并保留一位小数
float money = 1.011;
NSLog(@"%.1f",money);

推荐阅读更多精彩内容