# C语言编译
gcc main.c -o my_program:GNU Compiler Collectiongcc main.c func.c -o my_program:多文件开发
# Xcode配置
- 代码练习:新建项目 - MacOS -
Command Line Tool - 关闭代码提示:
Preferences -> Text Editing -> Code Completion -> Disable - 为project添加多个target:
- 新建
Project -> Targets -> + - 切换:编译链接旁边切换目标target
- 新建
- 打开僵尸对象调试:
Edit Schemes -> Run -> Diagnostics -> Zombie Objects -> Enable
# Xcode快捷键
- 同时编辑一个变量
control+command+e
# Xcode文档安装
Window -> Developer Documentation
# C语言基础
- 程序只会从
main函数开始执行 - 函数:
void funcName(int a, int b) - int范围在
-21亿~21亿左右 float只能存7位有效位数,定义加f,如f1 = 1.1fdouble可以存16位有效位数,直接写一个小数就是double类型的char只能存一个字符:char a = 'a',必须用单引号int赋值小数会被截断,如int a = 1.9,结果为1- 布尔值:c语言用
int表示,1为真,0为假 - 只读变量:
const int a = 1; - 只读指针变量:
const int * p = &a;,无法通过指针修改变量的值int a = 1; int b = 2; const int * p = &a; *p = 5; // 报错1
2
3
4 - 只读变量指针:
int * const p = &a;,无法通过指针修改变量的值int a = 1; int b = 2; int * const p = &a; p = &b; // 报错1
2
3
4 - 完全只读指针:
int const * const p = &a;
# printf格式化
int: %d,float: %f,char: %c,string: %s,double: %lf- 格式化位数:
%.2f,如%.2f表示保留两位小数%5d,如%5d表示输出的数字前面补空格,共5位printf("%05d\n", 3);:,用0补齐五位,输出00003- 八进制:以
0开头,07777777,打印占位符%o - 十六进制:以
0x开头,0x12acde,打印占位符%x
- 格式化地址位:
%p,例::printf("%p\n", &a); - 对齐输出:
%-5d,如%-5d表示输出的数字前面补空格,共5位,输出1 - 对齐输出变量控制:
printf("编号:%-*d姓名:%-*s性别:%-*s成绩:%-*.0lf\n", 4, stu.id, 15, stu.name, 5, gender, 10, stu.score);
# scanf
- 接收用户输入,赋值给指定变量地址
scanf("%d", &a); - 使用
&获取变量的地址int main(int argc, const char * argv[]) { int num = 0; int pwd = 0; printf("请输入账号:"); scanf("%d", &num); if (num == 1) { printf("请输入密码:"); scanf("%d", &pwd); if (pwd == 123456) { printf("密码正确\n"); } else { printf("密码错误\n"); } } else { printf("请输入数字\n"); } return 0; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 - 输入多个字符用空格隔开
scanf("%d%d", &num, &pwd);1 - 可以自定义分隔符
scanf("%d-%d", &num, &pwd);1 - scan是从
缓冲区获取数据,如果缓冲区有数据,则不会等用户输入int num = 0; int pwd = 0; scanf("%d", &num); rewind(stdin); // 清空缓冲区 printf("num = %d\n", num); // 输入 10 20 scanf("%d", &pwd); printf("pwd = %d\n", pwd);1
2
3
4
5
6
7
8
9 - 清空
scanf的缓冲区:rewind(stdin); - 清空缓冲区函数
void clearInputBuffer(void) { int c; while ((c = getchar()) != '\n' && c != EOF); }1
2
3
4
# 运算
- 如果算数运算的表达式类型是一致的,那么结果也会是这个类型
int num1 = 10;
int num2 = 4;
double num3 = num1 / num2;
printf("num3 = %lf\n", num3); // num3 = 2.000000
1
2
3
4
2
3
4
# 内置方法
- 做随机数
#include <stdlib.h>
// 0-9 的随机数
printf("请输入分数:%d\n", arc4random_uniform(10));
// 10-20的随机数
printf("请输入分数:%d\n", arc4random_uniform(11) + 10);
1
2
3
4
5
6
7
2
3
4
5
6
7
- 最大值
INT32_MAX - 最小值
INT32_MIN
# switch-case
- 注意:
break,不然会继续执行下面的case - switch表达式的结果不可以是浮点数,否则会报错
- 如果要在case里面声明变量,就需要用大括号括起来这个case的语句
int score = 6; switch(score * 10) { case 60: { printf("刚好及格\n"); break; } case 70: printf("及格\n"); break; default: printf("不懂\n"); }1
2
3
4
5
6
7
8
9
10
11
12
13
# 函数声明
- 函数声明可以在函数调用之前声明
- 函数声明和函数定义可以放在同一个文件,也可以放在不同的文件
- 函数声明:
void funcName(int a, int b); - 函数定义:
int funcName(int a, int b) { return a + b; } - 只读形参:
void printArray(const int * arr, int len);
# 预处理指令
- 宏定义没有分号
; - 编译之前把程序中所有使用宏名的地方换成宏值
#define LEN 10#undef LEN: 删除宏定义,后续代码宏不可再使用- 条件编译指令:
#if、#ifdef、#ifndef、#else、#elif、#endif
- 分类
- 文件包含指令:
#include,把头文件内容复制到当前位置 - 宏指令:
#define - 条件编译指令:
#if、#ifdef、#ifndef、#else、#elif、#endif - 错误处理指令:
#error - 警告处理指令:
#warning - 运算指令:
#pragma
- 文件包含指令:
- 特点
- 以
#开头 - 没有分号
; - 预处理指令是编译器在
编译之前处理的,所以不能在函数里面使用
- 以
- 带参数宏,参数不需要加类型说明符
#define ADD(a, b) a + b int c = ADD(10, 20); printf("%d\n", c); // 301
2
3 - 条件编译指令
#if:只编译部分代码,条件只能是宏,因为是在编译阶段,还没有变量#define DEBUG 1 #define DEV 0 #define DEPLOY 0 #if DEBUG printf("DEBUG 模式!\n"); #elif DEV printf("DEV 模式!\n"); #elif DEPLOY printf("DEPLOY 模式!\n"); #endif1
2
3
4
5
6
7
8
9
10
11 - 条件编译指令
#ifdef:判断宏是否存在,存在就编译,不存在就不编译#ifdef DEBUG printf("DEBUG模式!!"); #endif1
2
3 - 条件编译指令
#ifndef:判断宏不存在,不存在就编译,存在就不编译 - 技巧:debug打log,只有在debug模式才会编译的log代码
#define DEBUG 1 #define DEV 0 #define DEPLOY 0 #if DEBUG #define LOG(str, ...) printf(str, ##__VA_ARGS__) #else #define LOG(str, ...) #endif LOG("DEBUG 模式!\n"); LOG("%d\n", a);1
2
3
4
5
6
7
8
9
10
11
12
# 进制
- 十进制:
10,打印占位符%d - 二进制:以
0b开头,ob101010111 - 八进制:以
0开头,07777777,打印占位符%o - 十六进制:以
0x开头,0x12acde,打印占位符%x
# 修饰符
- int
signed:有符号,默认unsigned:无符号short:短整型,占2个字节,short int可以省略intlong:长整型,32位操作系统占4个字节,64位系统占8个字节,long int可以省略intlong long:长长整型,任何系统都占8个字节,long long int可以省略int
# 数组
- 声明数组:
int arr[10]; - 声明直接赋值:
int arr[3] = {100, 2,10}; - 自动计算初始化长度:
int arr[] = {100, 2,10}; - 自动计算初始化值为0:
int arr[10] = {}; - 指定位置初始化值:
int arr[10] = {[1] = 3, [9] = 7}; - 使用直接赋值的方式,就不能使用
变量做长度,可以使用宏作为长度。- 错误示范:
int arr[len] = {100, 2,10}; - 正确示范:
int arr[LEN] = {100, 2,10};
- 错误示范:
- 长度计算:
sizeof(arr) / sizeof(arr[0]); - 当数组作为函数的参数的时候,会丢失数组的长度信息,所以需要使用
指针来传递数组。同属传入数组长度。- 例子:
void func(int *arr, int len);/void printArr(int arr[], int len); - 原因:函数参数是数组,那么该形参就是指针地址。
- 例子:
- 数组名是一个地址常量,赋之后无法修改
# 二维数组
声明:
int arr[3][4];直接赋值示例:
int arr[3][4] = { {1, 2, 3, 4}, {1, 2, 3, 4}, {1, 2, 3, 4}, }; printf("第二行第一列是:%d\n", arr[2][1]); // 2441
2
3
4
5
6指定行初始化
int arr[3][4] = { [1] = {1, 2, 3, 4}, [2] = {1, 233, 3, 4}, }; printf("第二行第一列是:%d\n", arr[2][1]);1
2
3
4
5全部初始化为0
int arr[3][4] = {0}; printf("第二行第一列是:%d\n", arr[2][1]); // 01
2求行数列数
int arr[3][4] = {0}; int rows = sizeof(arr) / sizeof(arr[0]); int columns = sizeof(arr[0]) / sizeof(arr[0][0]); int cells = sizeof(arr) / sizeof(arr[0][0]); printf("行数是:%d\n列数是:%d\n总元素个数是:%d\n", rows, columns, cells);1
2
3
4
5
6函数入参指定任意行列的数组:
void func(int rows, int cols, int arr[][cols]);
# 字符串
- 字符串的声明通常用指针声明,可以避免再赋值空间不够程序崩溃
- 字符串的声明:
char str[10];/char *str = "hello world"; - 字符串的初始化:
char str[] = "hello world"; - 打印字符串:
printf("%s", str); - 分割原理:字符串的最后一个多一位
\0,用来表示字符数组结束 - 面试题:
char str[] = "jack";
char str2[4] = "rose";
printf("%s\n", str2); // rosejack
// 原因:rose只被分配了4个字节,没位置存\0,jack被读取了
1
2
3
4
5
2
3
4
5
- 以字符数组的形式赋值:
char str[] = { 'j', 'a', 'c', 'k', '\0' }; - 获取字符串长度:
char str[10] = "jack";
unsigned long len = strlen(str);
printf("欢迎你:%s\n名字长度:%lu\n", str, len);
1
2
3
2
3
- 输入字符串:
scanf("%s", str);- 输入字符串时,如果输入的字符串长度大于数组长度,则会出现越界,程序会崩溃
- 输入空格会被认为结束字符串,不会被存储到数组中
- 常用函数
<stdio.h>puts(str);:打印字符串gets(str): 从键盘输入字符串,并返回字符串长度,也是超出长度会崩溃
<string.h>strlen(str):获取字符串长度strcpy(str1, str2):将字符串str2复制到str1中,修改str1,长度不够运行会崩溃strcat(str1, str2):将字符串str2连接到str1的末尾,修改str1,长度不够运行会崩溃strstr(str1, str2),在一个字符串中查找另一个字符串首次出现的位置strcmp(str1, str2):比较字符串str1和str2- 返回值:完全相等返回
0
- 返回值:完全相等返回
fputs()/fopen/fclose: 将字符串str写入文件/控制台- 输出到标准输出流
char* str = "hello world"; // 输出到标准输出流 fputs(str, stdout);1
2
3 - 输出到文件流
/** * 文件打开,用一个file指针指向文件 * fopen第二个参数 * w: 写入 * r: 读取 * a: 追加 */ FILE *fp = fopen( "/Users/dreamarts/Documents/coderhdy/c-study/00_base/12_指针/test.txt", "w"); /** 输入 */ fputs(str, fp); /** 关闭读取文件 */ fclose(fp);1
2
3
4
5
6
7
8
9
10
11
12
13
14
- 输出到标准输出流
fgets():从文件流中读取字符串,返回读取的字符串长度。安全,推荐用法- 读取标准输入流数据
char str[5]; printf("请输入:\n"); fgets(str, sizeof(str), stdin); // 输入的最后一位可能是\n,在字符串长度不够的时候(5 - 1 = 4) unsigned long len = strlen(str); if (str[len] == '\n') { str[len] = '\0'; } printf("%s\n", str);1
2
3
4
5
6
7
8
9
10
11 - 读取文件流数据
FILE* fp = fopen("/Users/dreamarts/Documents/coderhdy/c-study/00_base/12_指针/test.txt", "r"); char str[100]; fgets(str, sizeof(str), fp); printf("%s\n", str); fclose(fp);1
2
3
4
5
- 读取标准输入流数据
# 指针
- 指针变量声明:
int *p;/int* p;/int * p; - 指针变量赋值:
p = &a; - 指针变量取值:
*p = 100; - 指针存
NULL值,被访问时会报错 - 形参指针修改:
void add(int *pNum1, int *pNum2) { *pNum1 += *pNum2; } int main(int argc, const char *argv[]) { int num1 = 1; int num2 = 2; add(&num1, &num2); printf("num1 = %d\n", num1); // 3 return 0; }1
2
3
4
5
6
7
8
9
10
11
12
13 - 声明二级指针:
int **p = &pNum;,二级指针只能存储一级指针的地址 - 二级指针使用:
*(*p),*p是二级指针指向的一级指针,*(*p)是二级指针指向的一级指针指向的值 - 指针的加减法,单位是指针自身单位变量:
p++,- 如果指针指向的是数组,则
p++会移动到下一个数组元素int arr[] = {1, 2, 3, 4, 5}; int *p = &arr[0]; for (int i = 0; i < 5; i++) { printf("第%d个元素是:%d\n", i, *(p + i)); // 遍历数组 }1
2
3
4
5// 数组名也是指针 int arr[] = {1, 2, 3, 4, 5}; for (int i = 0; i < 5; i++) { printf("第%d个元素是:%d\n", i, *(arr + i)); // 遍历数组 }1
2
3
4
5 - 如果指针指向的是结构体,则
p++会移动到下一个结构体成员。
- 如果指针指向的是数组,则
- 指针数组:是一个数组,数组中每个元素都是指针。例:
int* p[3] = {&a, &b, &c}; - 指针之间的减法:两个指针之间相差多少个元素。
int arr[6] = {1, 2, 3, 4, 5, 6}; int* p1 = &arr[2]; int* p2 = &arr[4]; long diff = p2 - p1; printf("%ld\n", diff); // 21
2
3
4
5
# 指针和字符变量
- 全局变量的指针字符变量:都存储在内存的Data区中。
- 局部变量的指针字符变量:字符串存储在内存的Data区中,字符串的结束符是
\0。// 局部变量: // 存储在栈中的字符串 char str1[] = "jack"; // 存储在Data区中的字符串 char* str2 = "hello";1
2
3
4
5 - 以指针方式存储的字符串无法通过下标修改
char* str = "hello"; str[0] = 'H'; // 报错 char* str3 = "hello"; str3 = "rose"; // 可以1
2
3
4
5 - 声明指针字符串的时候,会从Date区寻找是否有相同的字符串,如果有则返回该字符串的指针,如果没有则创建一个字符串,并返回该字符串的指针。
char* str1 = "hello"; char* str2 = "hello"; char* str3 = "hello"; printf("str1 = %p \n", str1); // 相同地址 printf("str1 = %p \n", str2); // 相同地址 printf("str1 = %p \n", str3); // 相同地址 printf("str1 = %s \n", str1); // hello printf("str1 = %s \n", str2); // hello printf("str1 = %s \n", str3); // hello1
2
3
4
5
6
7
8
9
10
11 - 等价:
str[i] == *(str + i)char* str = "asdfghasdfgasdfgh"; int count = 0; int i = 0; while (*(str + i) != '\0') { if (*(str + i) == 'a') { count++; } i++; } // 等价:str[i] == *(str + i) while (str[i] != '\0') { if (str[i] == 'a') { count++; } i++; }1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18 - 存储多个字符串,建议使用指针数组,可以存储不固定长度的字符串
char* str[] = {"hello", "world", "rose"};
for (int i = 0; i < 3; i++) {
printf("%s\n", str[i]);
}
1
2
3
4
2
3
4
# 指针和函数
- 指针指向函数,可以调用
char* getWeekDay(int weekDayNum) {
switch(weekDayNum) {
case 1:
return "Monday";
case 2:
return "Tuesday";
case 3:
return "Wednesday";
case 4:
return "Thursday";
case 5:
return "Friday";
case 6:
return "Saturday";
case 7:
return "Sunday";
default:
return "错误值";
}
}
int main(int argc, const char * argv[]) {
// 指向外部函数的指针
// 函数返回值 (*指针函数名)(参数)
char* (*getWeekDayFn)(int weekDayNum) = getWeekDay;
// 通过指针调用函数
char* weekDay = getWeekDayFn(4);
printf("%s\n", weekDay);
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
# 内存分区
- 栈:存储局部变量,函数调用参数,函数返回地址,函数返回值。
- 堆:存储动态分配的内存。允许程序员动态分配内存。
- Bss区:存储未初始化的全局变量,静态变量,常量。
- Data区:存储已经初始化的全局变量,静态变量,常量,字符串。
- Text区:存储代码。
- 字符串存储在Data区中,字符串的结束符是
\0。所以函数可以返回字符串 - 但是不能修以字符指针存储在Data区中的改字符串。
char* getWeekDay(int weekDayNum) {
switch(weekDayNum) {
case 1:
return "Monday";
case 2:
return "Tuesday";
case 3:
return "Wednesday";
case 4:
return "Thursday";
case 5:
return "Friday";
case 6:
return "Saturday";
case 7:
return "Sunday";
default:
return "错误值";
}
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
# 内存使用
- 申请内存:
malloc()/calloc()/realloc()/free() malloc():申请内存,返回内存地址,失败返回NULL,没申请到会有NULL,使用完后需要释放。#include <stdlib.h> int *p = malloc(sizeof(int) * 10); if (p == NULL) { printf("内存申请失败"); return 1; } for (int i = 0; i < 10; i++) { p[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", p[i]); } //释放 free(p);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15calloc():常用,申请内存,返回内存地址,失败返回NULL,并且初始化内存为0#include <stdlib.h> // calloc使用 int *p = calloc(10, sizeof(int)); if (p == NULL) { printf("内存申请失败"); return 1; } for (int i = 0; i < 10; i++) { p[i] = i + 1; } for (int i = 0; i < 10; i++) { printf("%d ", p[i]); } //释放 free(p);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16realloc():(扩容)重新分配内存,返回内存地址,失败返回NULL#include <stdlib.h> int *p = calloc(10, sizeof(int)); int *p1 = realloc(p, 11); if (p1 == NULL) { printf("内存申请失败"); return 1; } for (int i = 0; i < 11; i++) { p1[i] = i + 1; } for (int i = 0; i < 11; i++) { printf("%d\n", p1[i]); } //释放 free(p1);1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16free():释放内存,释放内存地址,释放内存后,内存地址变为NULL
# static和extern
- 如果要声明全局变量,必须使用
static/extern修饰。 - 声明全局变量要声明在.h文件中,实现在.c文件中。
- 区别:
static只有当前模块能访问的变量。extern是外部变量,是全局变量,全局变量是所有源文件都能访问的变量。
extern int num = 10; // 外部能直接操作
static int age = 20; // 外部只能通过本文件的函数访问操作
int getAge() {
return age;
}
void addAge() {
age++;
}
1
2
3
4
5
6
7
8
9
2
3
4
5
6
7
8
9
# static与malloc
- static:静态变量,静态函数,静态代码块,静态方法,静态成员变量,静态成员函数。
- static修饰全局变量:让全局变量变成只有源文件能访问的变量
- static修饰局部变量:让局部变量变成只有函数能访问的变量,第一次访问初始化,后续访问复用。
static和malloc的区别
- calloc可以初始化内存为0
- 生命周期:
- static:全局变量,函数,代码块,成员变量,成员函数,生命周期是整个程序。
- malloc:动态分配内存,生命周期是可以由程序员调用
free释放掉
# 结构体
- 结构体:一种特殊的自定义的数据类型,可以存储多个变量,变量类型可以不同。
- 结构体变量赋值形式是拷贝成员变量的值进行赋值
- 初始化语法
struct Student { char* name; int gender; float height; }; int main(int argc, const char * argv[]) { struct Student ming; ming.name = "小明"; ming.gender = 1; ming.height = 1.75; return 0; }1
2
3
4
5
6
7
8
9
10
11
12
13 - 创建结构体时初始化
struct Student { char* name; int gender; float height; } ming,hong,fang;1
2
3
4
5 - 简易初始化
struct Student ming = {"小明", 1, 1.75};1struct Student ming = { .name = "小明", .gender = 1, .height = 1.75 };1
2
3
4
5 - 创建结构体指针
struct Student fang = {"小芳", 0, 1.65}; struct Student* pStu = &fang; // 指针访问有简写 (*pStu).name = "小方"; // 简写 pStu -> height = 1.70;1
2
3
4
5
6
# 枚举
- 枚举值的命名规范:以枚举类型名称开头,单词首字母大写
enum WeekDay {
WeekDayMonday = 1,
WeekDayTuesday,
WeekDayWednesday,
WeekDayThursday,
WeekDayFriday,
WeekDaySaturday,
WeekDaySunday
}
enum Gender {
Male = 1,
Female = 2
}
Gender gender = Male;
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
# typedef
- 作用:定义类型别名,方便使用,减少代码量,提高代码可读性。
- 常用简写,为匿名结构体/枚举重命名
// 结构体
typedef struct {
int age;
char* name;
} Student;
Student xiaoming = {19, "小明"};
// 枚举
typedef enum {
DirectionEast,
DirectionWest
} Direction;
Direction d1 = DirectionEast;
1
2
3
4
5
6
7
8
9
10
11
12
13
2
3
4
5
6
7
8
9
10
11
12
13