一、概述
最近家里有点事,趁在家的这段时间,复习一下C语言核心知识,后的底层开发、音视频开发、跨平台开发、算法等方向的进一步研究与学习埋下伏笔
本篇文章接着上一篇继续对C语言的核心语法知识进行复习
二、C 语言核心语法|函数
函数
1、函数的分类
前面已经说过,C语言中的函数就是面向对象中的"方法",C语言的函数可以大概分为3类:
- 主函数:
- 也就是main函数。每个程序中只能有一个、也必须有一个主函数。无论主函数写在什么位置,C程序总是从主函数开始执行
- 自定义函数:
- 开发人员自定义的函数,可有可无,数目不限
- 库函数:
- C语言提供的库函数,例如stdio.h中的输出函数printf()和输入函数scanf()
2、函数的声明
函数的声明
- 函数声明会告诉编译器函数名称及如何调用函数。函数的实际主体可以单独定义。
- 函数声明包括以下几个部分:
return_type function_name( parameter list );
- 针对上面定义的函数 max(),以下是函数声明:
int max(int num1, int num2);
- 在函数声明中,参数的名称并不重要,只有参数的类型是必需的,因此下面也是有效的声明:
int max(int, int);
- 当您在一个源文件中定义函数且在另一个文件中调用函数时,函数声明是必需的。在这种情况下,您应该在调用函数的文件顶部声明函数。
3、函数的定义
- C 语言中的函数定义的一般形式如下:
return_type function_name( parameter list ) { body of the function }
- 在 C 语言中,函数由一个函数头和一个函数主体组成。下面列出一个函数的所有组成部分:
- 返回类型: 一个函数可以返回一个值。return_type 是函数返回的值的数据类型。有些函数执行所需的操作而不返回值,在这种情况下,return_type 是关键字 void。
- 函数名称: 这是函数的实际名称。函数名和参数列表一起构成了函数签名。
- 参数: 参数就像是占位符。当函数被调用时,您向参数传递一个值,这个值被称为实际参数。参数列表包括函数参数的类型、顺序、数量。参数是可选的,也就是说,函数可能不包含参数。
- 函数主体: 函数主体包含一组定义函数执行任务的语句。
- 例子:
/* 函数返回两个数中较大的那个数 */ int max(int num1, int num2) { /* 局部变量声明 */ int result; if (num1 > num2) result = num1; else result = num2; return result; }
- 标准C语言中,函数的
定义顺序
是有讲究的 - 默认情况下,只有
后面定义
的函数才可以调用前面定义
过的函数- 第5行定义的main函数调用了第1行的sum函数,这是合法的。
- 如果调换下sum函数和main函数的顺序,在标准的C编译器环境下是不合法的(不过在Xcode中只是警告,Xcode中用的是Clang编译器)
int sum(int a, int b) { return a + b; } int main() { int c = sum(1, 4); return 0; }
- 如果想把其他函数的定义写在main函数后面,而且main函数能正常调用这些函数,那就必须在main函数前面作一下函数的声明
// 只是做个函数声明,并不用实现 int sum(int a, int b); int main() { int c = sum(1, 4); return 0; } // 函数的定义(实现) int sum(int a, int b) { return a + b; }
- 我们在第2行做了sum函数的声明,然后在第6行(main函数中)就可以正常调用sum函数了。
- 函数的声明格式:
返回值类型 函数名 (参数1, 参数2, ...)
- 可以省略参数名称,比如上面的sum函数声明可以写成这样:
int sum(int, int);
- 只要你在main函数前面声明过一个函数,main函数就知道这个函数的存在,就可以调用这个函数。究竟这个函数是做什么用,还要看函数的定义。如果只有函数的声明,而没有函数的定义,那么程序将会在链接时出错。
- 在大型的C程序中,为了分模块进行开发,一般会将函数的声明和定义(即实现)分别放在2个文件中,函数声明放在.h头文件中,函数定义放在.c源文件中
4、函数的形参和实参
函数参数
- 在定义函数时,函数名后面的()中定义的变量称为形式参数(形参);在调用函数时传入的值称为实际参数(实参)。
- 形参就像函数内的其他局部变量,在进入函数时被创建,退出函数时被销毁。
// b是test函数的形参(形式参数) void test(int b) { b = 9; // 改变了形参b的值 } int main() { int a = 10; printf("函数调用前的a:%d\n", a); test(a); // a是test函数的实参(实际参数) printf("函数调用后的a:%d", a); return 0; }
如果是基本数据类型作为函数的形参,那是简单的值传递,将实参a的值赋值给了形参b,相当于
int a = 10;
int b = a;
b = 9;
a和b是分别有着不同内存地址的2个变量,因此改变了形参b的值,并不会影响实参a的值。
上述代码的输出结果为:
调用类型 | 描述 |
---|---|
传值调用 | 该方法把参数的实际值复制给函数的形式参数。在这种情况下,修改函数内的形式参数不会影响实际参数。 |
引用调用 | 通过指针传递方式,形参为指向实参地址的指针,当对形参的指向操作时,就相当于对实参本身进行的操作。 |
5. 函数的调用
//
// main.c
// 05-函数
//
// Created by VanZhang on 2022/5/16.
//
#include <stdio.h>
//函数声明
int max(int num1, int num2);
int main(int argc, const char * argv[]) {
//找出函数中最大值
printf("找出函数中最大值,%d \n",max(66,88));
return 0;
}
//定义
int max(int num1, int num2) {
return (num1 > num2) ? num1 : num2;//三目运算
}
输出:
找出函数中最大值,88
6. printf函数
这是在stdio.h
中声明的一个函数,因此使用前必须加入#include <stdio.h>
,使用它可以向标准输出设备
(比如屏幕)输出数据
6.1 用法
1> printf(字符串)
printf("Hello, World!");
输出结果是:
2> printf(字符串, 格式符参数)
// 使用常量作参数
printf("My age is %d\n", 26);
// 也可以使用变量
int age = 17;
printf("My age is %d", age);
- 格式符
%d
表示以有符号的十进制
形式输出一个整型,格式符参数中的26和age会代替%d的位置。 - 第2行代码中的
\n
是个转义字符,表示换行,所以输出了第一句"My age is 26"后会先换行,再输出"My age is 27"
输出结果:
- 如果去掉第2行中的\n,将会是这样的效果
输出结果:
总结:左边字符串中格式符的个数 必须跟 右边格式符参数的个数一样;格式符的类型决定了格式符参数的类型,比如使用%d,说明对应的格式符参数必须是整型。
再举个例子:
printf("My age is %d and no is %d", 27, 1);
输出结果:
6.2 常用的格式符及其含义
6.3 格式符还可以添加一些精细的格式控制
1> 输出宽度
- 我们先看看默认的整型输出
printf("The price is %d.", 14);
- 输出结果(注意,后面是有个点的):
- 如果我把%d换成%4d:
printf("The price is %4d.", 14);
输出结果:
,你会发现"is"跟"14"的距离被拉开了
%4d的意思是输出宽度为4,而"14"的宽度为2,因此多出2个宽度,多出的宽度就会在左边用空格填补,因此你会看到"14"左边多了2个空格;如果实际数值宽度比较大,比如用%4d输出宽度为6的"142434",那就会按照实际数值宽度6来输出。
printf("The price is %4d.", 142434);
输出结果:
,"142434"的输出宽度为6
- 如果换成%-4d
printf("The price is %-4d.", 14);
输出结果:
,你会发现"14"跟"."的距离被拉开了
%-4d表示输出宽度为4,如果比实际数值宽度大,多出的宽度会在右边用空格填补;如果4比实际数值宽度小,就按照实际数值的宽度来输出
6.4 浮点数的小数位数
- 我们先看下默认的浮点数输出
printf("My height is %f", 179.95f);
输出结果:
,默认是输出6位小数
* 如果只想输出2位小数,把%f换成%.2f即可
C printf("My height is %.2f", 179.95f);
输出结果:
- 当然,可以同时设置输出宽度和小数位数
printf("My height is %8.1f", 179.95f);
输出结果:
,输出宽度为8,保留1位小数
7. scanf函数
这也是在stdio.h中声明的一个函数,因此使用前必须加入#include <stdio.h>。调用scanf函数时,需要传入变量的地址作为参数,scanf函数会等待标准输入设备(比如键盘)输入数据,并且将输入的数据赋值给地址对应的变量
7.1 简单用法
printf("Please input your age:");
int age;
scanf("%d", &age);
printf("Your age is %d.", age);
- 运行程序,执行完第1行代码,控制台会输出一句提示信息:
- 执行到第4行的scanf函数时,会等待用户的键盘输入,并不会往后执行代码。scanf的第1个参数是"%d",说明要求用户以10进制的形式输入一个整数。这里要注意,scanf的第2个参数传递的不是age变量,而是age变量的地址&age,&是C语言中的一个地址运算符,可以用来获取变量的地址。
- 接着我们可以在提示信息后面输入个8:
- (由于Xcode自身的问题,我们只能在控制台输入宽度为1的数据,如果想输入宽度大于1的数据,比如输入27,可以从别的地方复制个27,再粘贴到控制台)
- 输入完毕后,敲一下回车键,目的是告诉scanf函数我们已经输入完毕了,scanf函数会将输入的8赋值给age变量
- scanf函数赋值完毕后,才会往后执行代码,执行到第6行时,控制器会输出:
7.2 其他用法
7.2.1 用scanf函数接收3个数值,在这里,每个数值之间用中划线-隔开
1 int a, b, c;
2 scanf("%d-%d-%d", &a, &b, &c);
3
4 printf("a=%d, b=%d, c=%d", a, b, c);
- 注意第2行,3个%d之间是用中划线-隔开的,因此我们在每输入一个整数后都必须加个中划线-,比如这样输入 ,不然在给变量赋值的时候会出问题
- 所有的数值都输入完毕后敲回车键,scanf函数会依次给变量a、b、c赋值,接着输出
注意:数值之间的分隔符是任意的,不一定要用中划线-,可以是逗号、空格、星号*、井号#等等,甚至是英文字母
// 逗号,
scanf("%d,%d,%d", &a, &b, &c); // 输入格式:10,14,20
// 井号#
scanf("%d#%d#%d", &a, &b, &c); // 输入格式:10#14#20
// 字母x
scanf("%dx%dx%d", &a, &b, &c); // 输入格式:10x14x20
7.2.2 用scanf函数接收3个数值,每个数值之间用空格隔开
1 int a, b, c;
2 scanf("%d %d %d", &a, &b, &c);
3
4 printf("a=%d, b=%d, c=%d", a, b, c);
- 注意第2行,3个%d之间是用空格隔开的,我们在每输入一个整数后必须输入一个分隔符,分隔符可以是空格、tab、回车
- 用空格做分隔符
- 用tab做分隔符
- 用回车做分隔符
三、C 语言核心语法|变量作用域
1. 作用域 规则
任何一种编程中,作用域是程序中定义的变量所存在的区域,超过该区域变量就不能被访问。C 语言中有三个地方可以声明变量:
- 在函数或块内部的局部变量
- 在所有函数外部的全局变量
- 在形式参数的函数参数定义中
让我们来看看什么是局部变量、全局变量和形式参数。
局部变量
在某个函数或块的内部声明的变量称为局部变量。它们只能被该函数或该代码块内部的语句使用。局部变量在函数外部是不可知的。下面是使用局部变量的实例。在这里,所有的变量 a、b 和 c 是 main() 函数的局部变量。
void main(){
//局部变量
int a, b;
int c;
//初始化局部变量
a = 10;
b = 20;
c = a + b;
//%d:以十进制形式输出带符号整数(正数不输出符号)
printf("values of a = %d,b = %d and c = %d \n", a, b, c);
}
输出:
values of a = 10,b = 20 and c = 30
全局变量
全局变量是定义在函数外部,通常是在程序的顶部。全局变量在整个程序生命周期内都是有效的,在任意的函数内部能访问全局变量。
全局变量可以被任何函数访问。也就是说,全局变量在声明后整个程序中都是可用的。下面是使用全局变量和局部变量的实例:
//全局变量声明
int g;
void main(){
int a, b;
//初始化局部变量
a = 10;
b = 20;
//全部变量赋值
g = a + c;
printf("values of a = %d,bc = %d and g = %d \n", a, c, g);
}
输出:
values of a = 10,bc = 30 and g = 40
形式参数
函数的参数,形式参数,被当作该函数内的局部变量,如果与全局变量同名它们会优先使用。下面是一个实例:
int sumA(int a, int b) {
printf("value of a in sum() = %d\n", a);
printf("value of b in sum() = %d\n", b);
return x + y;
}
void main(){
int a, b,c;
//初始化局部变量
a = 10;
b = 20;
c = sumA(a, b);
printf("value of c in main() = %d\n", c);
}
输出:
value of a in main() = 30
全局变量和局部变量的区别
- 全局变量保存在内存的全局存储区中,占用静态的存储单元;
- 局部变量保存在栈中,只有在所在函数被调用时才动态地为变量分配存储单元。
初始化局部变量和全局变量的默认值
数据类型 | 初始化默认值 |
---|---|
int | 0 |
char | '\0' |
float | 0 |
double | 0 |
pointer | NULL |
四、C 语言核心语法|数组与枚举
1. 数组
为了让大家更好地学习和理解数组,我们先来认识一下内存中的"地址"。
1.1 地址
- 计算机中的内存是
以字节为单位的存储空间
- 内存的每一个字节都有一个唯一的编号,这个编号就称为地址。
- 凡存放在内存中的程序和数据都有一个地址
- 也就是说,一个函数也有自己的内存地址。
- 当定义一个变量时,系统就分配一个带有唯一地址的存储单元来存储这个变量。比如:
char a = 'A'; // A的ASCII值为65 int b = 66;
- 假设是在16bit环境下,系统为a、b分别分配1个字节、2个字节的存储单元。变量存储单元的第一个字节的地址就是该变量的地址
- 可以看出,变量a的地址是ffc3;变量b的地址是ffc1。内存中存储的都是2进制数据。
- 假设是在16bit环境下,系统为a、b分别分配1个字节、2个字节的存储单元。变量存储单元的第一个字节的地址就是该变量的地址
- 在调试过程中,我们采取打印的方式查看变量的地址:
int c = 10; // 以16进制形式输出地址 printf("16进制:%x\n", &c); // 以10进制形式输出地址 printf("10进制:%d", &c);
输出结果:
1.2 C语言数组
C 语言支持数组数据结构
- 它可以存储一个固定大小的
相同类型元素
的顺序集合
- 数组是用来存储
一系列数据
,但它往往被认为是一系列相同类型
的变量。 - 数组的声明并不是声明一个个单独的变量
- 比如 number0、number1、...、number99,
- 而是声明一个数组变量
- 比如 numbers,然后使用 numbers[0]、numbers[1]、...、numbers[99] 来代表一个个单独的变量。数组中的特定元素可以通过索引访问。
- 所有的数组都是由连续的内存位置组成。最低的地址对应第一个元素,最高的地址对应最后一个元素。
1.3 声明数组
在 C 中要声明一个数组,需要指定元素的类型和元素的数量,如下所示:
type arrayName [ arraySize ];
这叫做一维数组。arraySize 必须是一个大于零的整数常量,type 可以是任意有效的 C 数据类型。例如,要声明一个类型为 double 的包含 10 个元素的数组 balance,声明语句如下:
double balance[10];
- 数组的定义的形式为:类型 数组名[元素个数]
int a[5];
- []只能放在数组名的后面,下面的都是错误写法:
int[5] a; // 错误 int[] b; // 错误
- []里面的个数必须是一个固定值,可以是常量(比如6、8)、常量表达式(比如3+4、5*7)。绝对不能使用变量或者变量表达式来表示元素个数,大多数情况下不要省略元素个数(当数组作为函数的形参和数组初始化时除外)
- 下面的都是正确写法:
int a[5]; // 整型常量 int b['A']; // 字符常量,其实就是65 int c[3*4]; // 整型常量表达式
- 下面的都是错误写法:
int a[]; // 没有指定元素个数,错误 int i = 9; int a[i]; // 用变量做元素个数,错误
- 下面的都是正确写法:
1.4 一维数组的存储
定义数组时,系统将按照数组类型和个数分配一段连续的存储空间来存储数组元素,如int a[3]占据了连续的6字节存储空间(在16位环境下,一个int类型占用2个字节)。要注意的是,数组名代表着整个数组的地址,也就是数组的起始地址。
注意:其实a不算是变量,是个常量,它代表着数组的地址。上图把a放到变量一栏是为了方便大家理解数组结构。
数组a的地址是ffc1,a[0]的地址是ffc1,a[1]的地址是ffc3,a[2]的地址是ffc5。因此a == &a[0],即第一个元素的地址就是整个数组的地址
1.5 一维数组的初始化
- 初始化的一般形式是:类型 数组名[元素个数] = {元素1, 元素2, ...};
int a[2] = {8, 10};
- 其实相当于:
int a[2]; a[0] = 8; a[1] = 10;
- 其实相当于:
- 注意的是:C语言中编译器是不会对数组下标越界进行检查的,所以自己访问数组元素时要小心
- 元素值列表可以是数组所有元素的初值,也可以是前面部分元素的初值
int a[4] = {2, 5};
- 当数组为整型时,初始化未确定初值的元素,默认为0,所以上面的a[2]、a[3]都为0
- 当对全部数组元素都赋初值时,可以省略元素个数
int a[] = {2, 5, 7};
- 说明数组a的元素个数是3
- 数组初始化时的赋值方式只能用于数组的定义,定义之后只能一个元素一个元素地赋值
下面的写法是错误的:1 int a[3]; 2 a[3] = {1, 2, 3}; // 错误 3 a = {1, 2, 3}; // 错误
其实为什么是错误的写法呢?我们可以简要分析一下。
- 1> 第2行的a[3]代表着访问数组的第4个元素,首先这里已经是数组下标越界了;就算没有越界,给a[3]赋值时也应该赋一个int类型的整数,不应该是{}。
- 2> 第3行的a是数组名,代表着数组的地址,它是个常量!给常量赋值,那肯定错了!
1.6 访问数组元素
double value = balance[1]
例子:
//
// main.c
// 06-数组、枚举、指针
//
// Created by VanZhang on 2022/5/17.
//
#include <stdio.h>
//数组
void test1(int arrLength) {
// type arrayName[ arraySize ] = {元素1,元素2...};
int listLength = arrLength>0?arrLength:10;
// int listLength = arrLength?:10;
//定义两个长度为 10 的整数数组
int xPoint[listLength], yPoint[listLength];
//初始化数组元素
for (int i = 0; i < listLength; i++) {
xPoint[i] = 2 * i;
yPoint[i] = 2 * i -1;
}
//计算数组的: 元素个数 = 数组总内存大小/单个元素大小 (因为所有元素都是相同类型的!!!)
//总的大小除以其中一个大小就得到了 数组长度
unsigned long count = sizeof(xPoint)/sizeof(xPoint[0]);
printf("整数数组 n 的长度: %lu \n", count);
//输出元素中的数据
for (int k = 0; k < count; ++k) {
printf("Element[%d]]=(%d,%d)\n",k,xPoint[k],yPoint[k]);
}
}
int main(int argc, const char * argv[]) {
test1(20);
return 0;
}
输出:
整数数组 n 的长度: 20
Element[0]]=(0,-1)
Element[1]]=(2,1)
Element[2]]=(4,3)
Element[3]]=(6,5)
Element[4]]=(8,7)
Element[5]]=(10,9)
Element[6]]=(12,11)
Element[7]]=(14,13)
Element[8]]=(16,15)
Element[9]]=(18,17)
Element[10]]=(20,19)
Element[11]]=(22,21)
Element[12]]=(24,23)
Element[13]]=(26,25)
Element[14]]=(28,27)
Element[15]]=(30,29)
Element[16]]=(32,31)
Element[17]]=(34,33)
Element[18]]=(36,35)
Element[19]]=(38,37)
Program ended with exit code: 0
1.7 C 中数组详解
在 C 中,数组是非常重要的,我们需要了解更多有关数组的细节。下面列出了 C 程序员必须清楚的一些与数组相关的重要概念:
概念 | 描述 |
---|---|
多维数组 | C 支持多维数组。多维数组最简单的形式是二维数组。 |
传递数组给函数 | 您可以通过指定不带索引的数组名称来给函数传递一个指向数组的指针。 |
从函数返回数组 | C 允许从函数返回数组。 |
指向数组的指针 | 您可以通过指定不带索引的数组名称来生成一个指向数组中第一个元素的指针。 |
1.8 一维数组与函数参数
如果忘记了实参
和形参
的意思,可以回看前文中 关于函数的篇幅 提及的 实参
和形参
* 一维数组的元素作为函数实参,与同类型的简单变量作为实参一样,是单向的值传递,即数组元素的值传给形参,形参的改变不影响实参 ```C // b是test函数的形参(形式参数) void test(int b) { b = 9; }
int main()
{
int a[3];
a[0] = 10;
printf("函数调用前的a[0]:%d\n", a[0]);
test(a[0]); // a[0]是test函数的实参(实际参数)
printf("函数调用后的a[0]:%d", a[0]);
return 0;
}
```
输出结果:
- 大家都知道,数组名代表着整个数组的地址
- 如果一维数组的名字作为函数实参,传递的是整个数组,,
- 即形参数组和实参数组完全等同,是存放在同一存储空间的同一个数组。
- 这样形参数组修改时,实参数组也同时被修改了。形参数组的元素个数可以省略。
// b是test函数的形参(形式参数) void test(int b[]) { // 也可以写int b[3] b[0] = 9; } int main() { int a[3]; a[0] = 10; printf("函数调用前的a[0]:%d\n", a[0]); test(a); // a是test函数的实参(实际参数) printf("函数调用后的a[0]:%d", a[0]); return 0; }
输出结果
2. 二维数组
2.1 二维数组的定义
定义形式:类型 数组名[行数][列数]
int a[2][3]; // 共2行3列,6个元素
2.2 二维数组的存储
- C语言把二维数组当作是一维数组的集合,即二维数组是一个特殊的一维数组:
- 它的元素是一维数组。例如int a[2][3]可以看作由一维数组a[0]和一维数组a[1]组成
- 这两个一维数组都包含了3个int类型的元素
- 二维数组的存放顺序是按行存放的,先存放第一行的元素,再存放第2行的元素。
- 例如int a[2][3]的存放顺序是:a[0][0] → a[0][1] → a[0][2] → a[1][0] → a[1][1] → a[1][2]
- 再来看看在内存中的存储情况,例如int a[2][2]
- (注意:a[0]、a[1]也是数组,是一维数组,而且a[0]、a[1]就是数组名,因此a[0]、a[1]就代表着这个一维数组的地址)
- 1> 数组a的地址是ffc1,数组a[0]的地址也是ffc1,即a = a[0];
- 2> 元素a[0][0]的地址是ffc1,所以数组a[0]的地址和元素a[0][0]的地址相同,即a[0] = &a[0][0];
- 3> 最终可以得出结论:a = a[0] = &a[0][0],以此类推,可以得出a[1] = &a[1][0]
3. 二维数组的初始化
- 按行进行初始化
int a[2][3] = { {2, 2, 3}, {3, 4, 5} };
- 按存储顺序进行初始化(先存放第1行,再存放第2行)
int a[2][3] = {2, 2, 3, 3, 4, 5};
- 对部分元素进行初始化
int a[2][3] = { {2}, {3, 4} }; int b[3][3] = { { }, { , , 2}, {1, 2, 3}};
- 如果只初始化了部分元素,可以省略行数,但是不可以省略列数
int a[][3] = {1, 2, 3, 4, 5, 6}; int a[][3] = {{1, 2, 3}, {3, 5}, {}};
- 有些人可能想不明白,为什么可以省略行数,但不可以省略列数。也有人可能会问,可不可以只指定行数,但是省略列数?
- 其实这个问题很简单,如果我们这样写:
int a[2][] = {1, 2, 3, 4, 5, 6}; // 错误写法
大家都知道,二维数组会先存放第1行的元素,由于不确定列数,也就是不确定第1行要存放多少个元素,所以这里会产生很多种情况,可能1、2是属于第1行的,也可能1、2、3、4是第一行的,甚至1、2、3、4、5、6全部都是属于第1行的
三维乃至更多维的数组就不再提及了,大家以此类推。
3.1 二维数组:平面坐标
//二维数组「平面坐标」【大数组 内部 包含若干小数组】
void test2(int arrLength) {
// type arrayName[ arraySize ] = {元素1,元素2...};
int listLength = arrLength>0?arrLength:10;
// int listLength = arrLength?:10;
//定义两个长度为 10 的整数数组
// int xPoint[listLength], yPoint[listLength];
int xyPoints[listLength][2];
//初始化数组元素
// for (int i = 0; i < listLength; i++) {
// xPoint[i] = 2 * i;
// yPoint[i] = 2 * i -1;
// }
int x,y;
for (int i = 0; i < listLength; i++) {
x = 2*i;
y = 2*i-1;
printf("i:%d------(x:%d,y:%d)\n",i,x,y);
for (int j = 0; j < 2; j++) {
printf("j:%d\n",j);
xyPoints[i][j] = (j==0)?x:y;
}
}
//计算数组的: 元素个数 = 数组总内存大小/单个元素大小 (因为所有元素都是相同类型的!!!)
//总的大小除以其中一个大小就得到了 数组长度
// unsigned long count = sizeof(xPoint)/sizeof(xPoint[0]);
unsigned long count1 = sizeof(xyPoints)/sizeof(xyPoints[0]);
unsigned long count2 = sizeof(xyPoints[0])/sizeof(xyPoints[0][0]);
printf("数组第一维度的长度: %lu \n", count1);
printf("数组第二维度的长度: %lu \n", count2);
//输出元素中的数据
// for (int k = 0; k < count; ++k) {
// printf("Element[%d]]=(%d,%d)\n",k,xPoint[k],yPoint[k]);
// }
for (int k = 0; k < count1; ++k) {
printf("Element[%d]=(x:%d,y:%d)\n",k,xyPoints[k][0],xyPoints[k][1]);
}
}
int main(int argc, const char * argv[]) {
// test1(20);
test2(20);
return 0;
}
输出:
i:0------(x:0,y:-1)
j:0
j:1
i:1------(x:2,y:1)
j:0
j:1
i:2------(x:4,y:3)
j:0
j:1
i:3------(x:6,y:5)
j:0
j:1
i:4------(x:8,y:7)
j:0
j:1
i:5------(x:10,y:9)
j:0
j:1
i:6------(x:12,y:11)
j:0
j:1
i:7------(x:14,y:13)
j:0
j:1
i:8------(x:16,y:15)
j:0
j:1
i:9------(x:18,y:17)
j:0
j:1
i:10------(x:20,y:19)
j:0
j:1
i:11------(x:22,y:21)
j:0
j:1
i:12------(x:24,y:23)
j:0
j:1
i:13------(x:26,y:25)
j:0
j:1
i:14------(x:28,y:27)
j:0
j:1
i:15------(x:30,y:29)
j:0
j:1
i:16------(x:32,y:31)
j:0
j:1
i:17------(x:34,y:33)
j:0
j:1
i:18------(x:36,y:35)
j:0
j:1
i:19------(x:38,y:37)
j:0
j:1
数组第一维度的长度: 20
数组第二维度的长度: 2
Element[0]=(x:0,y:-1)
Element[1]=(x:2,y:1)
Element[2]=(x:4,y:3)
Element[3]=(x:6,y:5)
Element[4]=(x:8,y:7)
Element[5]=(x:10,y:9)
Element[6]=(x:12,y:11)
Element[7]=(x:14,y:13)
Element[8]=(x:16,y:15)
Element[9]=(x:18,y:17)
Element[10]=(x:20,y:19)
Element[11]=(x:22,y:21)
Element[12]=(x:24,y:23)
Element[13]=(x:26,y:25)
Element[14]=(x:28,y:27)
Element[15]=(x:30,y:29)
Element[16]=(x:32,y:31)
Element[17]=(x:34,y:33)
Element[18]=(x:36,y:35)
Element[19]=(x:38,y:37)
Program ended with exit code: 0
3.2 二维数组:空间坐标
//二维数组「三维空间坐标」
void test3(int arrLength) {
int listLength = arrLength>0?arrLength:10;
// int listLength = arrLength?:10;
//定义两个长度为 10 的整数数组
// int xPoint[listLength], yPoint[listLength];
int xyzPoints[listLength][3];
//初始化数组元素
// for (int i = 0; i < listLength; i++) {
// xPoint[i] = 2 * i;
// yPoint[i] = 2 * i -1;
// }
int x,y,z;
int value;
for (int i = 0; i < listLength; i++) {
x = 2*i;
y = 2*i-1;
z = 2*i+1;
printf("i:%d------(x:%d,y:%d,z:%d)\n",i,x,y,z);
for (int j = 0; j < 3; j++) {
printf("j:%d\n",j);
if (j==0) {
value = x;
}else if (j==1) {
value = y;
}else{
value = z;
}
xyzPoints[i][j]= value;
}
}
//计算数组的: 元素个数 = 数组总内存大小/单个元素大小 (因为所有元素都是相同类型的!!!)
//总的大小除以其中一个大小就得到了 数组长度
// unsigned long count = sizeof(xPoint)/sizeof(xPoint[0]);
unsigned long count1 = sizeof(xyzPoints)/sizeof(xyzPoints[0]);
unsigned long count2 = sizeof(xyzPoints[0])/sizeof(xyzPoints[0][0]);
printf("数组第一维度的长度: %lu \n", count1);
printf("数组第二维度的长度: %lu \n", count2);
//输出元素中的数据
// for (int k = 0; k < count; ++k) {
// printf("Element[%d]]=(%d,%d)\n",k,xPoint[k],yPoint[k]);
// }
for (int k = 0; k < count1; ++k) {
printf("Element[%d]=(x:%d,y:%d,z:%d)\n",k,xyzPoints[k][0],xyzPoints[k][1],xyzPoints[k][2]);
}
}
int main(int argc, const char * argv[]) {
// test1(20);
// test2(20);
test3(20);
return 0;
}
输出:
i:0------(x:0,y:-1,z:1)
j:0
j:1
j:2
i:1------(x:2,y:1,z:3)
j:0
j:1
j:2
i:2------(x:4,y:3,z:5)
j:0
j:1
j:2
i:3------(x:6,y:5,z:7)
j:0
j:1
j:2
i:4------(x:8,y:7,z:9)
j:0
j:1
j:2
i:5------(x:10,y:9,z:11)
j:0
j:1
j:2
i:6------(x:12,y:11,z:13)
j:0
j:1
j:2
i:7------(x:14,y:13,z:15)
j:0
j:1
j:2
i:8------(x:16,y:15,z:17)
j:0
j:1
j:2
i:9------(x:18,y:17,z:19)
j:0
j:1
j:2
i:10------(x:20,y:19,z:21)
j:0
j:1
j:2
i:11------(x:22,y:21,z:23)
j:0
j:1
j:2
i:12------(x:24,y:23,z:25)
j:0
j:1
j:2
i:13------(x:26,y:25,z:27)
j:0
j:1
j:2
i:14------(x:28,y:27,z:29)
j:0
j:1
j:2
i:15------(x:30,y:29,z:31)
j:0
j:1
j:2
i:16------(x:32,y:31,z:33)
j:0
j:1
j:2
i:17------(x:34,y:33,z:35)
j:0
j:1
j:2
i:18------(x:36,y:35,z:37)
j:0
j:1
j:2
i:19------(x:38,y:37,z:39)
j:0
j:1
j:2
数组第一维度的长度: 20
数组第二维度的长度: 3
Element[0]=(x:0,y:-1,z:1)
Element[1]=(x:2,y:1,z:3)
Element[2]=(x:4,y:3,z:5)
Element[3]=(x:6,y:5,z:7)
Element[4]=(x:8,y:7,z:9)
Element[5]=(x:10,y:9,z:11)
Element[6]=(x:12,y:11,z:13)
Element[7]=(x:14,y:13,z:15)
Element[8]=(x:16,y:15,z:17)
Element[9]=(x:18,y:17,z:19)
Element[10]=(x:20,y:19,z:21)
Element[11]=(x:22,y:21,z:23)
Element[12]=(x:24,y:23,z:25)
Element[13]=(x:26,y:25,z:27)
Element[14]=(x:28,y:27,z:29)
Element[15]=(x:30,y:29,z:31)
Element[16]=(x:32,y:31,z:33)
Element[17]=(x:34,y:33,z:35)
Element[18]=(x:36,y:35,z:37)
Element[19]=(x:38,y:37,z:39)
Program ended with exit code: 0
4. 枚举
枚举是 C 语言中的一种基本数据类型,它可以让数据更简洁,更易读。
枚举语法定义格式为:
enum 枚举名 {枚举元素1,枚举元素2,……};
接下来我们举个例子,比如:一星期有 7 天,如果不用枚举,我们需要使用 #define 来为每个整数定义一个别名:
#define MON 1
#define TUE 2
#define WED 3
#define THU 4
#define FRI 5
#define SAT 6
#define SUN 7
这个看起来代码量就比较多,接下来我们看看使用枚举的方式:
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
这样看起来是不是更简洁了。
注意: 第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。我们在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。
可以在定义枚举类型时改变枚举元素的值:
enum season {spring, summer=3, autumn, winter};
没有指定值的枚举元素,其值为前一元素加 1。也就说 spring 的值为 0,summer 的值为 3,autumn 的值为 4,winter 的值为 5
枚举变量的定义
前面我们只是声明了枚举类型,接下来我们看看如何定义枚举变量。
我们可以通过以下三种方式来定义枚举变量
1、先定义枚举类型,再定义枚举变量
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
};
enum DAY day;
2、定义枚举类型的同时定义枚举变量
enum DAY
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
3、省略枚举名称,直接定义枚举变量
enum
{
MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;
例子:
//枚举
enum {
//改变第一个枚举元素的初始值,才会改变其枚举选项的值,且顺为改变其他 值
// MON=3,TUE,WED,THU,FRI,SAT,SUN
MON=1,TUE,WED,THU,FRI,SAT,SUN
}day;
void test4(void){
//遍历一周
for (day = MON; day <= SUN; day++) {
printf("周: %d \n", day);
}
enum color{
red=1, green, blue ,black
};
//声明一个 color类型的 枚举变量
enum color favorite_color;
// ask user to choose color
printf("请输入你喜欢的颜色: (1. red, 2. green, 3. blue): ");
scanf("%d", &favorite_color);
// 输出结果
switch (favorite_color)
{
case red:
printf("你喜欢的颜色是红色\n");
break;
case green:
printf("你喜欢的颜色是绿色\n");
break;
case blue:
printf("你喜欢的颜色是蓝色\n");
break;
case black:
printf("你喜欢的颜色是黑色\n");
break;
default:
printf("你没有选择你喜欢的颜色\n");
}
//将整数转换为枚举
enum Day
{
saturday,
sunday,
monday,
tuesday,
wednesday,
thursday,
friday
} ;
int a = 1;
//声明一个 day类型的 枚举变量
enum Day weekend;
weekend = (enum Day)a;
printf("weekend:%d \n",weekend);
}int main(int argc, const char * argv[]) {
// test1(20);
// test2(20);
// test3(20);
test4();
return 0;
}
输出:
周: 1
周: 2
周: 3
周: 4
周: 5
周: 6
周: 7
请输入你喜欢的颜色: (1. red, 2. green, 3. blue): 0
你没有选择你喜欢的颜色
weekend:1
Program ended with exit code: 0
五、C语言核心语法|字符与字符串
1. 字符串
- C语言中没有String这种类型。其实字符串就是字符序列,由多个字符组成,所以在C语言中,我们可以用字符数组来存储字符串。
- 字符串可以看做是一个特殊的字符数组,为了跟普通的字符数组区分开来,应该在字符串的尾部添加了一个结束标志'\0'。
- '\0'是一个ASCII码值为0的字符,是一个空操作符,表示什么也不干。所以采用字符数组存放字符串,赋值时应包含结束标志'\0'。
在 C 语言中,字符串实际上是使用 null 字符'\0'
终止的一维字符数组。因此,一个以 null 结尾的字符串,包含了组成字符串的字符。
下面的声明和初始化创建了一个 "Hello" 字符串。由于在数组的末尾存储了空字符,所以字符数组的大小比单词 "Hello" 的字符数多一个。
char ch[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
也可以使用以下简写模式:
char ch[6] = "Hello"
字符串在 C/C++ 中内存表示:
其实,您不需要把 null 字符放在字符串常量的末尾。C 编译器会在初始化数组时,自动把 '\0' 放在字符串的末尾。让我们尝试输出上面的字符串:
//字符数组 字符串
void test8(void) {
//定义一个 char 数组
char string[6] = {'H', 'e', 'l', 'l', 'o', '\0'};
//简写
char string2[6] = "Hello";
//%s:输出字符串
printf("string message : %s\n", string);
printf("string2 message : %s\n", string2);
}
int main(int argc, const char * argv[]) {
// test1(20);
// test2(20);
// test3(20);
// test4();
// test5();
// test6();
// test7();
test8();
return 0;
}
输出:
string message : Hello
string2 message : Hello
Program ended with exit code: 0
C 中对字符串操作的 API
序号 | 函数 & 目的 |
---|---|
1 | strcpy(s1, s2); 复制字符串 s2 到字符串 s1。 |
2 | strcat(s1, s2); 连接字符串 s2 到字符串 s1 的末尾。 |
3 | strlen(s1); 返回字符串 s1 的长度。 |
4 | strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。 |
5 | strchr(s1, ch); 返回一个指针,指向字符串 s1 中字符 ch 的第一次出现的位置。 |
6 | strstr(s1, s2); 返回一个指针,指向字符串 s1 中字符串 s2 的第一次出现的位置。 |
例子:
#include <string.h>
//对字符串操作的 系统API
void test2(void) {
//字符串操作
char str1[12] = "Hello";
char str2[12] = "World";
char str3[12];
size_t len;
//将 str1 复制到 str3
strcpy(str3, str1);
printf("strcpy (str3,str1) :%s\n", str3);
// strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
int isEqual = strcmp(str1, str3);
if (isEqual==0) {
printf("字符串相同\n");
}else {
printf("字符串bu同\n");
}
//拼接字符串 str1 + str2
strcat(str1, str2);
printf("strcat(str1,str2) :%s\n", str1);
//返回字符串的长度
len = strlen(str1);
printf("strlen(str1) :%zu\n", len);
// strcmp(s1, s2); 如果 s1 和 s2 是相同的,则返回 0;如果 s1<s2 则返回小于 0;如果 s1>s2 则返回大于 0。
isEqual = strcmp(str1, str3);
if (isEqual==0) {
printf("字符串相同\n");
}else {
printf("字符串bu同\n");
}
// char *l_Idx = strchr(str1, 'l');
// char*str_Idx = strstr(str1, str3);
}
int main(int argc, const char * argv[]) {
// test1();
test2();
return 0;
}
输出:
strcpy (str3,str1) :Hello
字符串相同
strcat(str1,str2) :HelloWorld
strlen(str1) :10
字符串bu同
Program ended with exit code: 0
六、C语言核心语法|指针与回调函数
1. 指针******
学习 C 语言的指针既简单又有趣。通过指针,可以简化一些 C 编程任务的执行,还有一些任务,如动态内存分配
,没有指针是无法执行的。所以,想要成为一名优秀的 C 程序员,学习指针是很有必要的。
正如您所知道的,每一个变量都有一个内存位置,每一个内存位置都定义了可使用连字号(&)运算符访问的地址,它表示了在内存中的一个地址。请看下面的实例,它将输出定义的变量地址:
void main(){
int var1;
char var2[10];
double *var3;
//%p : 输出指针地址
printf("var1 变量的地址:%p \n", &var1);
printf("var2 变量的地址:%p \n", &var2);
printf("var3 变量的地址:%p \n", &var3);
}
输出:
var1 变量的地址:0x7ffee7e976b8
var2 变量的地址:0x7ffee7e976be
var3 变量的地址:0x7ff7bfeff270
通过上面的实例,我们了解了什么是内存地址以及如何访问它。接下来让我们看看什么是指针。
什么是指针?
指针是一个变量,其值为另一个变量的地址
,即内存位置的直接地址
。
就像其他变量或常量一样,您必须在使用指针存储其他变量地址之前,对其进行声明。指针变量声明的一般形式为:
type *var-name
在这里,type 是指针的基类型,它必须是一个有效的 C 数据类型,var-name 是指针变量的名称。
用来声明指针的星号 *
与乘法中使用的星号是相同的。但是,在这个语句中,星号是用来指定一个变量是指针。以下是有效的指针声明:
int *i; //一个整型的指针
double *d;//double 型指针
float *f;//浮点型指针
char *ch//字符型指针
所有实际数据类型,不管是整型、浮点型、字符型,还是其他的数据类型,对应指针的值的类型都是一样的,都是一个代表内存地址的长的十六进制数。
不同数据类型的指针之间唯一的不同是,指针所指向的变量或常量的数据类型不同。
如何使用指针?
使用指针时会频繁进行以下几个操作:定义一个指针变量
、把变量地址赋值给指针
、访问指针变量中可用地址的值
。
这些是通过使用一元运算符 ***** 来返回位于操作数所指定地址的变量的值。下面的实例涉及到了这些操作:
例子:
//如何使用指针
int var = 66;//实际变量的声明
int *ip;//指针变量的声明
ip = &var; //指针变量中存储 var 的地址
printf("var 的地址 : %p \n", var);
//在指针变量中存储的地址
printf("ip 的地址:%p \n", ip);
//使用指针访问地址
printf("ip 指针对应的地址:%p \n", *ip);
//使用指针访问地址对应的值
printf("ip 指针对应的地址:%d \n", *ip);
/*
一个指针变量 占据内存的大小,跟指针的类型无关:
一个指针在32位的计算机上,占4个字节;
一个指针在64位的计算机上,占8个字节。
一个指针占几个字节?原理是什么呢?https://blog.csdn.net/IOSSHAN/article/details/88944637
*/
int *a;
long*b;
float*c;
double*d;
char*e;
printf("一个int类型指针占据内存大小:%lu \n",sizeof(a));
printf("一个long类型指针占据内存大小:%lu \n",sizeof(b));
printf("一个float类型指针占据内存大小:%lu \n",sizeof(c));
printf("一个double类型指针占据内存大小:%lu \n",sizeof(d));
printf("一个char类型指针占据内存大小:%lu \n",sizeof(e));
输出:
var 的地址 : 0x42
ip 的地址:0x7ffee96eb6b4
ip 指针对应的地址:0x42
ip 指针对应的地址:66
一个int类型指针占据内存大小:8
一个long类型指针占据内存大小:8
一个float类型指针占据内存大小:8
一个double类型指针占据内存大小:8
一个char类型指针占据内存大小:8
C 中的 NULL 指针
在变量声明的时候,如果没有确切的地址可以赋值,为指针变量赋一个 NULL 值是一个良好的编程习惯。赋为 NULL 值的指针被称为空指针。
NULL 指针是一个定义在标准库中的值为零的常量。请看下面的程序:
void main(){
//赋值一个 NULL 指针
int *ptr = NULL;
printf("ptr 的地址是: %p \n", ptr);
//检查一个空指针
if (ptr) printf("如果 ptr 不是空指针,则执行"); else printf("如果 ptr 是空指针,则执行");
}
输出:
ptr 的地址是: 0x0 ptr 是空指针
C 指针详解
在 C 中,有很多指针相关的概念,这些概念都很简单,但是都很重要。下面列出了 C 程序员必须清楚的一些与指针相关的重要概念:
概念 | 描述 |
---|---|
指针的算术运算 | 可以对指针进行四种算术运算:++、--、+、- |
指针数组 | 可以定义用来存储指针的数组。 |
指向指针的指针 | C 允许指向指针的指针。 |
传递指针给函数 | 通过引用或地址传递参数,使传递的参数在调用函数中被改变。 |
从函数返回指针 | C 允许函数返回指针到局部变量、静态变量和动态内存分配。 |
2. 函数指针与回调函数
函数指针是指向函数的指针变量。
通常我们说的指针变量是指向一个整型、字符型或数组等变量,而函数指针是指向函数。
函数指针可以像一般函数一样,用于调用函数、传递参数。
函数指针变量的声明:
typedef int (*fun_ptr)(int,int)//声明一个指向同样参数,返回值得函数指针类型
例子:
//函数指针
//声明一个函数指针:可以指向该类型的函数(返回值类型相同、形参列表相同)
typedef int(*func_Rint_I1int_I2int)(int,int);
//声明一个函数
int max(int num1, int num2) {
return (num1 > num2) ? num1 : num2;
}
void test6(void) {
//函数为 func_Rint_I1int_I2int 类型的函数
func_Rint_I1int_I2int p =*max;
int a, b, c, d;
printf("请输入三个数字:\n");
scanf("%d %d %d", &a, &b, &c);
//与直接调用函数等价,d = max(max(a,b),c);
d = p(p(a, b), c);
printf("最大数字是: %d \n", d);
}
int main(int argc, const char * argv[]) {
// test1(20);
// test2(20);
// test3(20);
// test4();
// test5();
test6();
return 0;
}
输入输出:
请输入三个数字:
1
3
5
最大数字是: 5
Program ended with exit code: 0
回调函数
函数指针变量可以作为某个函数的参数
来使用的,回调函数就是一个通过函数指针调用的函数
。
简单讲:回调函数是由别人的函数执行时调用你实现的函数
。
例子:
例子中 populate_array 函数定义了三个参数,其中第三个参数是函数的指针,通过该函数来设置数组的值。
实例中我们定义了回调函数 getNextRandomValue,它返回一个随机值,它作为一个函数指针传递给 populate_array 函数。
populate_array 将调用 10 次回调函数,并将回调函数的返回值赋值给数组。
/*
回调函数
函数指针变量可以作为某个函数的参数来使用的,回调函数就是一个通过函数指针调用的函数。
简单讲:回调函数是由别人的函数执行时调用你实现的函数。
*/
#include <stdio.h>
#include <stdlib.h>
//回调函数
void populate_array(int *array, size_t arraySize, int(*getNextValue)(void)) {
printf("array 地址:%p \n", array);
for (size_t i = 0; i < arraySize; i++) {
array[i] = getNextValue();
printf(" array[%zu] ,存储值:%d \n", i, array[i]);
}
}
//获取一个随机数
int getNextRandomValue(void) {
return rand();
}
void test7(void) {
//回调函数
int array[10];
printf("Int array 地址:%p \n", array);
int count =(sizeof(array)/sizeof(array[0]));
printf("===============数组存储========================\n");
populate_array(array, count, getNextRandomValue);
printf("===============遍历数组========================\n");
for (int i = 0; i < count; ++i) {
printf(" array[%d] , 对应值为:%d \n", i, array[i]);
}
}
int main(int argc, const char * argv[]) {
// test1(20);
// test2(20);
// test3(20);
// test4();
// test5();
// test6();
test7();
return 0;
}
输出:
Int array 地址:0x7ff7bfeff260
===============数组存储========================
array 地址:0x7ff7bfeff260
array[0] ,存储值:16807
array[1] ,存储值:282475249
array[2] ,存储值:1622650073
array[3] ,存储值:984943658
array[4] ,存储值:1144108930
array[5] ,存储值:470211272
array[6] ,存储值:101027544
array[7] ,存储值:1457850878
array[8] ,存储值:1458777923
array[9] ,存储值:2007237709
===============遍历数组========================
array[0] , 对应值为:16807
array[1] , 对应值为:282475249
array[2] , 对应值为:1622650073
array[3] , 对应值为:984943658
array[4] , 对应值为:1144108930
array[5] , 对应值为:470211272
array[6] , 对应值为:101027544
array[7] , 对应值为:1457850878
array[8] , 对应值为:1458777923
array[9] , 对应值为:2007237709
Program ended with exit code: 0
专题系列文章
1. iOS底层原理前知识
- 01-探究iOS底层原理|综述
- 02-探究iOS底层原理|编译器LLVM项目【Clang、SwiftC、优化器、LLVM】
- 03-探究iOS底层原理|LLDB
- 04-探究iOS底层原理|ARM64汇编
2. 基于OC语言探索iOS底层原理
- 05-探究iOS底层原理|OC的本质
- 06-探究iOS底层原理|OC对象的本质
- 07-探究iOS底层原理|几种OC对象【实例对象、类对象、元类】、对象的isa指针、superclass、对象的方法调用、Class的底层本质
- 08-探究iOS底层原理|Category底层结构、App启动时Class与Category装载过程、load 和 initialize 执行、关联对象
- 09-探究iOS底层原理|KVO
- 10-探究iOS底层原理|KVC
- 11-探究iOS底层原理|探索Block的本质|【Block的数据类型(本质)与内存布局、变量捕获、Block的种类、内存管理、Block的修饰符、循环引用】
- 12-探究iOS底层原理|Runtime1【isa详解、class的结构、方法缓存cache_t】
- 13-探究iOS底层原理|Runtime2【消息处理(发送、转发)&&动态方法解析、super的本质】
- 14-探究iOS底层原理|Runtime3【Runtime的相关应用】
- 15-探究iOS底层原理|RunLoop【两种RunloopMode、RunLoopMode中的Source0、Source1、Timer、Observer】
- 16-探究iOS底层原理|RunLoop的应用
- 17-探究iOS底层原理|多线程技术的底层原理【GCD源码分析1:主队列、串行队列&&并行队列、全局并发队列】
- 18-探究iOS底层原理|多线程技术【GCD源码分析1:dispatch_get_global_queue与dispatch_(a)sync、单例、线程死锁】
- 19-探究iOS底层原理|多线程技术【GCD源码分析2:栅栏函数dispatch_barrier_(a)sync、信号量dispatch_semaphore】
- 20-探究iOS底层原理|多线程技术【GCD源码分析3:线程调度组dispatch_group、事件源dispatch Source】
- 21-探究iOS底层原理|多线程技术【线程锁:自旋锁、互斥锁、递归锁】
- 22-探究iOS底层原理|多线程技术【原子锁atomic、gcd Timer、NSTimer、CADisplayLink】
- 23-探究iOS底层原理|内存管理【Mach-O文件、Tagged Pointer、对象的内存管理、copy、引用计数、weak指针、autorelease
3. 基于Swift语言探索iOS底层原理
关于函数
、枚举
、可选项
、结构体
、类
、闭包
、属性
、方法
、swift多态原理
、String
、Array
、Dictionary
、引用计数
、MetaData
等Swift基本语法和相关的底层原理文章有如下几篇:
- 01-📝Swift5常用核心语法|了解Swift【Swift简介、Swift的版本、Swift编译原理】
- 02-📝Swift5常用核心语法|基础语法【Playground、常量与变量、常见数据类型、字面量、元组、流程控制、函数、枚举、可选项、guard语句、区间】
- 03-📝Swift5常用核心语法|面向对象【闭包、结构体、类、枚举】
- 04-📝Swift5常用核心语法|面向对象【属性、inout、类型属性、单例模式、方法、下标、继承、初始化】
- 05-📝Swift5常用核心语法|高级语法【可选链、协议、错误处理、泛型、String与Array、高级运算符、扩展、访问控制、内存管理、字面量、模式匹配】
- 06-📝Swift5常用核心语法|编程范式与Swift源码【从OC到Swift、函数式编程、面向协议编程、响应式编程、Swift源码分析】
4. C语言核心语法
- 01-复习C语言核心知识|综述
- 02-复习C语言核心知识|基本语法、数据类型、变量、常量、存储类、基本语句(判断语句、循环语句、go to语句)和运算
- 03-复习C语言核心知识|函数、作用域规则、数组、枚举、字符与字符串、指针
- 04-复习C语言核心知识|结构体、共用体、位域、输入&输出、文件读写
- 05-复习C语言核心知识|预处理、头文件、强制类型转换、错误处理、递归、内存管理
5. C++核心语法
- 01-C++核心语法|C++概述【C++简介、C++起源、可移植性和标准、为什么C++会成功、从一个简单的程序开始认识C++】
- 02-📝C++核心语法|C++对C的扩展【::作用域运算符、名字控制、struct类型加强、C/C++中的const、引用(reference)、函数】
- 03-📝C++核心语法|面向对象1【 C++编程规范、类和对象、面向对象程序设计案例、对象的构造和析构、C++面向对象模型初探】
- 04-📝C++核心语法|面向对象2【友元、内部类与局部类、强化训练(数组类封装)、运算符重载、仿函数、模板、类型转换、 C++标准、错误&&异常、智能指针】
- 05-📝C++核心语法|面向对象3【 继承和派生、多态、静态成员、const成员、引用类型成员、VS的内存窗口】
6. Vue全家桶
- 01-📝Vue全家桶核心知识|Vue基础【Vue概述、Vue基本使用、Vue模板语法、基础案例、Vue常用特性、综合案例】
- 02-📝Vue全家桶核心知识|Vue常用特性【表单操作、自定义指令、计算属性、侦听器、过滤器、生命周期、综合案例】
- 03-📝Vue全家桶核心知识|组件化开发【组件化开发思想、组件注册、Vue调试工具用法、组件间数据交互、组件插槽、基于组件的
- 04-📝Vue全家桶核心知识|多线程与网络【前后端交互模式、promise用法、fetch、axios、综合案例】
- 05-📝Vue全家桶核心知识|Vue Router【基本使用、嵌套路由、动态路由匹配、命名路由、编程式导航、基于vue-router的案例】
- 06-📝Vue全家桶核心知识|前端工程化【模块化相关规范、webpack、Vue 单文件组件、Vue 脚手架、Element-UI 的基本使用】
- 07-📝Vue全家桶核心知识|Vuex【Vuex的基本使用、Vuex中的核心特性、vuex案例】
7. 音视频技术核心知识
- 01-📝音视频技术核心知识|了解音频技术【移动通信技术的发展、声音的本质、深入了解音频】
- 02-📝音视频技术核心知识|搭建开发环境【FFmpeg与Qt、Windows开发环境搭建、Mac开发环境搭建、Qt开发基础】
- 03-📝音视频技术核心知识|Qt开发基础【
.pro
文件的配置、Qt控件基础、信号与槽】 - 04-📝音视频技术核心知识|音频录制【命令行、C++编程】
- 05-📝音视频技术核心知识|音频播放【播放PCM、WAV、PCM转WAV、PCM转WAV、播放WAV】
- 06-📝音视频技术核心知识|音频重采样【音频重采样简介、用命令行进行重采样、通过编程重采样】
- 07-📝音视频技术核心知识|AAC编码【AAC编码器解码器、编译FFmpeg、AAC编码实战、AAC解码实战】
- 08-📝音视频技术核心知识|成像技术【重识图片、详解YUV、视频录制、显示BMP图片、显示YUV图片】
- 09-📝音视频技术核心知识|视频编码解码【了解H.264编码、H.264编码、H.264编码解码】
- 10-📝音视频技术核心知识|RTMP服务器搭建【流媒体、服务器环境】
其它底层原理专题
1. 底层原理相关专题
2. iOS相关专题
- 01-iOS底层原理|iOS的各个渲染框架以及iOS图层渲染原理
- 02-iOS底层原理|iOS动画渲染原理
- 03-iOS底层原理|iOS OffScreen Rendering 离屏渲染原理
- 04-iOS底层原理|因CPU、GPU资源消耗导致卡顿的原因和解决方案