Objective-C 学习(C 语言基础)

编辑于2019年05月21日

Objective-C 语言是以 C 语言为基础的,但增加了对面向对象编程的基础。因此在学习 Objective-C 之前先学习一下 C 语言,虽然在学校的时候学习过,但是现在基本也已经还给老师了🥵。这篇文章记录了我觉得比较重要的点,一些通用的编程知识(条件语句、函数、循环等)则不包括。

数据类型

c_type

基本类型的信息如下(64位系统):

类型 字节 取值范围 输出格式
char 1 - %c
unsigned char 1 - -
short 2 -32768~32767 %hd
unsigned short 2 0~65535 %hu
int 4 -2147483648~214783647 %d
unsigned int 4 0~429467295 %u
long 8 -9223372036854775808~9223372036854775807 %ld
unsigned long 8 0~18446744073709551615 %lu
long long 8 - %lld
unsigned long long 8 - %lld
float 4 1.2E-38 到 3.4E+38 %f
double 8 2.3E-308 到 1.7E+308 %lf
long double 16 3.4E-4932 到 1.1E+4932 %llf

使用 sizeof(type) 可以得到某个类型的大小。

Bool 型

C 语言中没有 bool 类型,使用整型的 1 或 0 来表示。

指针

指针是一个变量,其值为另一个变量的内存地址。指针变量声明的一般形式为:

type *name;

// 如:int *ip; 

指针的值实质是内存单元(即字节)的编号,所以指针单独从数值上看,也是整数,一般用 16 进制表示。使用 & 运算符可以取得一个变量的地址。
如果一个类型占用的字节数大于 1,则其变量的地址就是地址值最小的那个字节的地址。
指针变量也是一种程序数据,那它也有自己的指针,因此就会有常说的指针的指针。
对一个指针解地址,就可以取到这个内存数据,解地址的写法就是在指针的前面加一个 * 号。
NULL 表示空指针。

void

在 C 语言中 void 表示无类型void * 则为无类型指针void * 可以指向任何类型的数据。

通常用来对函数的参数和返回值进行限定,当函数不需要返回值值时,必须使用 void 限定。

数组

数组用来存储一个固定大小的相同类型元素的顺序集合。在 C 中要声明一个数组,需要指定元素的类型和元素的数量:

type arrayName [ arraySize ];

可以逐个初始化数组,也可以使用一个初始化语句:

double balance[5] = {1000.0, 2.0, 3.4, 7.0, 50.0};

如果省略掉了数组的大小,数组的大小则为初始化时元素的个数。

数组名的值就是这个数组的第一个元素的地址。

初始化二维数组

下面的写法是一样的,会按照顺序一行一行的进行初始化。

int a[5][3]={ {80,75,92}, {61,65,71}, {59,63,70}, {85,87,90}, {76,77,85} };

int a[5][3]={ 80,75,92,61,65,71,59,63,70,85,87,90,76,77,85};

字符串

在 C 语言中,字符串实际上是使用 null 字符 '\0' 终止的一维字符数组,使用 %s 进行格式化输出。

char greeting[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
// or
char greeting[] = "Hello";

字符串字面值常量作为右值时,就是这个字符串对应的字符数组的名称,也就是这个字符串在内存中的地址,因此可以通过定义 char 类型的指针访问字符串:

char *greeting = "Hello";

结构体

结构体是 C 编程中另一种用户自定义的可用的数据类型,它允许存储不同类型的数据项。使用 struct 语句定义结构体:

struct Person {
    float heightInMeters;
    int weightInKilos;
} tom;

在结构的末尾,最后一个分号之前,可以指定一个或多个结构变量。通过 . 可以访问结构体的成员变量。

在声明类型为结构体的变量时,每次都要使用 struct 关键字,为了简化输入,可以使用 typedef 关键字为某个结构体声明一个新的类型。

typedef struct {
    float heightInMeters;
    int weightInKilos;
} Person;

如果 p 是一个结构体指针,则可以使用 p->attrName 的方法访问结构体的成员。p->attrName 等价于 (*p).attrName

共用体

共用体是一种特殊的数据类型,允许在相同的内存位置存储不同的数据类型。可以定义一个带有多成员的共用体,但是任何时候只能有一个成员带有值。共用体提供了一种使用相同的内存位置的有效方式。使用 union 语句定义共用体:

union [union tag]
{
   member definition;
   member definition;
   ...
   member definition;
} [one or more union variables];

枚举

枚举是 C 语言中的一种基本数据类型,它是一个集合,集合中的元素(枚举成员)是一些命名的整型常量,元素之间用逗号 , 隔开:

enum 枚举名 {枚举元素1,枚举元素2,……};

第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。

通过引用传参

C 语言中只有值传递(C++中有引用传递):调用函数时将实参的拷贝设置给形参。JS 中也是使用的值传递,但是在 js 中如果传递的是一个对象,在函数中修改对象属性也会导致实参发生变化。这种情况是因为传递的是引用(指针)的拷贝,实参和形参指向的是同一片内存,所以修改内容会导致变化,但是如果将形参重新赋值,是不会导致实参变化的。

C 语言中引用传参也就是传递变量的指针(形参是拷贝过的指针),在函数中如果修改指针所指地址的值,会导致变量的值发生变化,但是将形参指针重新赋值指向新的地址,不会导致实参指针发生变化。参考下面的代码:

#include <stdio.h>

void testStr(char *str){
    printf("str is %p\n", str);
    printf("str is %s\n", str);
    str = "surprise";
    printf("str is %p\n", str);
    printf("str is %s\n", str);
}

void testInt(int *num){
    *num = *num + 1;
    num = NULL;
}

int main(int argc, const char * argv[]) {
    char *str = "Hello World";
    testStr(str);
    printf("str is %s\n", str);
    printf("\n");

    int num = 1;
    printf("num is %p\n", &num);
    testInt(&num);
    printf("num is %d\n", num);
    printf("num is %p\n", &num);
    return 0;
}

运行的结果如下:

result

const

关键字 const 用来定义常量,如果一个变量被 const 修饰,那么它的值就不能再被改变。

创建常量的格式通常为:const type name = value;,其中 cosnttype 的位置可以互换。

cosnt 也可以和指针变量一起使用,可以用来限制指针变量本身(指针常量)或者指针指向的数据(常量指针):

const int *p1; // 常量指针,p1 指向一个常量,指向的内容不能被修改
int const *p2; // 常量指针
int * const p3; // 指针常量,p3 不能改变,即指向其他地址。

// 指向常量的常指针,指针本身和指向的内容都不能修改
const int * const p4;
int const * const p5;

在 C 语言中,单独定义 const 变量没有明显的优势,完全可以使用 #define 命令代替。const 通常用在函数形参中,如果形参是一个指针,为了防止在函数内部修改指针指向的数据,就可以用 const 来限制。

size_t strlen ( const char * str );
int strcmp ( const char * str1, const char * str2 );

当一个指针变量 str1const 限制时,并且类似 const char *str1 这种形式,说明指针指向的数据不能被修改;如果将 str1 赋值给另外一个未被 const 修饰的指针变量 str2,就有可能发生危险。因为通过 str1 不能修改数据,而赋值后通过 str2 能够修改数据了。因此不能将常量指针赋值给普通指针,但是可以将普通指针赋值给常量指针。

内存

内存中运行着很多程序,我们的程序只占用一部分空间,这部分空间又可以细分为以下的区域:

内存分区 说明
栈(stack) 存放程序中的局部变量(但不包括static声明的变量,static变量放在数据段中)。同时,在函数被调用时,栈用来传递参数和返回值。由于栈先进先出特点。所以栈特别方便用来保存/恢复调用现场。
(heap) 用来存放进程运行中被动态分配的内存段,它的大小并不固定,可动态扩张或缩减。当进程调用malloc分配内存时,新分配的内存就被动态添加到堆上,当进程调用free释放内存时,会从堆中剔除。
BSS段(bss segment) 通常是指用来存放程序中未初始化的全局变量的一块内存区域。BSS是英文Block Started by Symbol的简称。BSS段属于静态内存分配。
数据段(data segment) 通常是指用来存放程序中已初始化的全局变量 的一块内存区域。数据段属于静态内存分配。
代码段(code segment/text segment) 通常是指用来存放程序执行代码 的一块内存区域。这部分区域的大小在程序运行前就已经确定,并且内存区域通常属于只读, 某些架构也允许代码段为可写,即允许修改程序。在代码段中,也有可能包含一些 只读的常数变量 ,例如字符串常量等。程序段为程序代码在内存中的映射,一个程序可以在内存中多有个副本。

C 语言操作堆内存的函数

C 语言为内存的分配和管理提供了几个函数。这些函数可以在 <stdlib.h> 头文件中找到。

void *malloc(int num);
在堆区分配一块指定大小的内存空间,用来存放数据。这块内存空间在函数执行完成后不会被初始化,它们的值是未知的。

void *calloc(int num, int size);
在内存中动态地分配 num 个长度为 size 的连续空间,并将每一个字节都初始化为 0。所以它的结果是分配了 num*size 个字节长度的内存空间,并且每个字节的值都是0。

void free(void *address);
该函数释放 address 所指向的内存块,释放的是动态分配的内存空间。

void *realloc(void *address, int newsize);
该函数重新分配内存,把内存扩展到 newsize。

在 C 语言中,可以采用命令 #define 来定义宏。该命令允许把一个名称指定成任何所需的文本,例如一个常量值或者一条语句。在定义了宏之后,无论宏名称出现在源代码的何处,预处理器都会把它用定义时指定的文本替换掉。

没有参数的宏定义,采用如下形式:

#define 宏名称 替换文本

“替换文本”前面和后面的空格符不属于替换文本中的内容。替代文本本身也可以为空。

可以定义具有形式参数(简称“形参”)的宏。当预处理器展开这类宏时,它先使用调用宏时指定的实际参数(简称“实参”)取代替换文本中对应的形参。带有形参的宏通常也称为类函数宏。

#define 宏名称( [形参列表] ) 替换文本
#define 宏名称( [形参列表 ,] ... ) 替换文本

“形参列表”是用逗号隔开的多个标识符,它们都作为宏的形参。当使用这类宏时,实参列表中的实参数量必须与宏定义中的形参数量一样多

参考资料

iOS

希望对您能有帮助,打赏随意