记录一下c语言的学习过程,笔记如有错误,欢迎指出!!!
文章目录
- 1.gcc编译4步骤:
- 2.源码,反码,补码
- 3.输出格式
- 4.各类型字节数
- 5.特殊限定符
- 6.数组
- 1.一维数组名称
- 2.二维数组的名称
- 3.指针数组, 数组指针
- 4.一维数组==选择排序==
- 7.操作技巧
- 1.快捷导入代码:
- 2.传入main函数参数
- 8.static修饰的全局,局部变量和函数
- 9.指针
- 1.野指针,空指针,万能指针
- 2.const修饰的指针
- 3.指针和数组的区别
- 4.指针和函数
- 5. 指针的输入输出特性
- 6.指针和字符串
- 7.指针的步长
- 8.offsetof
- 9.字符串做指针
- 10.字符串拷贝,翻转,Sprintf
- 11.函数指针
- 1.函数指针和指针函数
- 2.函数指针做函数参数(回调函数)
1.gcc编译4步骤:
-
预处理 -E xxx.i 预处理文件
gcc -E xxx.c -o xxx.i
- 头文件展开。 — 不检查语法错误。 可以展开任意文件。
2)宏定义替换。 — 将宏名替换为宏值。
3)替换注释。 — 变成空行
4)展开条件编译 — 根据条件来展开指令。
-
编译 -S xxx.s 汇编文件
gcc -S hello.i -o hello.s
1)逐行检查语法错误。【重点】 — 整个编译4步骤中最耗时的过程。
2)将C程序翻译成 汇编指令,得到.s 汇编文件。
-
汇编 -c xxx.o 目标文件
gcc -c hello.s -o hello.o
1)翻译:将汇编指令翻译成对应的 二进制编码。
-
链接 无 xxx.exe 可执行文件。
gcc hello.o -o hello.exe
1)数据段合并
2)数据地址回填
3)库引入
2.源码,反码,补码
源码:
43 -> 00101011
-43 --> 10101011
反码:
43 -> 00101011
-43 --> 10101011
11010100
补码:(现今计算机采用的存储形式)
43 -> 00101011 : 正数不变
-43 --> 11010101 : 负数,最高位表符号位, 其余取反+1
3.输出格式
%d %u(无符号整型) %o(八进制) %x(十六进制) %hd(短整型) %hu (无符号短整型) %ld %lu(无符号长整形) %lld %llu %c %f %lf
%s :打印字符串
%x:打印16进制数
%u:打印无符号
%m.n: 打印实型时用到,一共有 m 位(整数、小数、小数点),n位小数。
%0m.nf: 其中 f:表示打印实型,一共有 m 位(整数、小数、小数点),n位小数。 0:表示不足 m 位时,用0凑够m位。
%%: 显示一个%。 转义字符’’ 对 % 转义无效。
%Ns:显示N个字符的字符串。不足N用空格向左填充。
%0Ns:显示N个字符的字符串。不足N用0向左填充。
%-Ns:显示N个字符的字符串。不足N用空格向右填充。
%p :显示地址,如数组地址,字符串地址
4.各类型字节数
char 类型:1字节 8个bit位。 数值位有7个。
有符号: -2^7 — 2^7-1 == -2^(8-1) – 2(8-1) -1
--》 -128 ~ 127
无符号: 0 ~ 2^8 -1
--》 0~255
不要超出该数据类型的存储范围。
short类型:2字节 16bit
有符号: -2^15 — 2^15-1 == -2^(16-1) – 2(16-1) -1
--》 -32768 ~ 32767
无符号: 0 ~ 2^8 -1
--》 0~65535
int 类型:4字节 -2^(32-1) – 2^(32-1)-1
有符号:
--》 -2147483648 ~ 2147483647
无符号: 0~2^32 -1
--》 0~4294967295
long类型:4字节
有符号:
--》 -2147483648 ~ 2147483647
无符号: 0~2^32 -1
--》 0~4294967295
longlong 类型:8字节
有符号:
--》 -2^(63) ~ 2^(63)-1
无符号:
--》 0~2^63-1
float类型:4字节,有符号
double: 8字节,有符号
5.特殊限定符
extern:表示声明。 没有内存空间。 不能提升。
const:限定一个变量为只读变量。
volatile:防止编译器优化代码。volatile int flg = 0;
register:定义一个寄存器变量。没有内存地址。register int a = 10;
6.数组
数组大小: sizeof(arr);
一行大小: sizeof(arr[0]): 二维数组的一行,就是一个一维数组。
一个元素大小:sizeof(arr[0][0]) 单位:字节
行数:row = sizeof(arr)/ sizeof(arr[0])
列数:col = sizeof(arr[0])/ sizeof(arr[0][0])
sizeof: 返回unsigned int类型,当unsigned int 和 int做运算,会转换成统一unsigned int数据类型
当数组名如 int arr[]做函数参数的时候,使用sizeof, 会退化成指针,指向数组中第一个元素的位置。
1.一维数组名称
有两种特殊情况,一维数组名不是 指向第一个元素的指针
-
sizeof
-
对数组名取地址 得到数组指针 步长是整个数组长度
//arr数组名 它是一个指针常量 指针的指向不可以修改的,而指针指向的值可以改 int * const a ;
arr[0] = 1000;
arr = NULL; //不可以//数组索引 可不可以为负数 答案:可以
int * p = arr;
p = p + 3;
printf("%d\n",p[-1]); //给人看的
printf("%d\n", *(p - 1)); //给机器看的
2.二维数组的名称
//二维数组的定义
int arr[3][3] = {
{1,2,3},
{4,5,6},
{7,8,9}
};
int arr2[3][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
int arr3[][3] = { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
//访问数组中的元素
printf("%d\n", arr[1][2]); //给人看
printf("%d\n", *(*(pArray + 1) + 2)); //给机器看
除了两种特殊情况外 ,二维数组名称是 指向第一个一维数组 数组指针
特殊情况1 sizeof
特殊情况2 对数组名取地址 &arr 获取的是二维数组的 数组指针 int(*p)[3][3] = &arr;
//二维数组做函数参数
void printArray(int (*array)[3], int row, int col)
void printArray(int array[][3], int row ,int col)
void printArray(int array[3][3], int row ,int col) //array[3][3] 等价于 一维数组指针 int (*array)[3]
3.指针数组, 数组指针
int *p[n] 指针数组
- int p[n] 就是一个指针数组,数据类型为int ,元素为地址(变量地址,数组地址,函数地址等),也就是说定义了n个不同指向int型的指针。在字符优先级表中,[]的优先级大于,所以,int *p[n] 就等价于int *(p[n])。
int (*p)[n] 数组指针
- 同上,根据优先级,int (*p)[n]表示定义一个指针,指向一个int[n]型的指针。
数组指针的定义方式
void test01()
{
int arr[5] = { 1, 2, 3, 4, 5 };
//1、先定义数组类型,再通过类型定义数组指针
typedef int(ARRARY_TYPE)[5];//ARRARY_TYPE 代表存放5个int类型元素的数组 的数组类型
//先定义数组指针类型,再通过类型定义数组指针
typedef int(*ARRARY_TYPE)[5];
//直接定义数组指针变量
int(* p )[5] = &arr;
4.一维数组选择排序
开始认定最小值下标为i,从j = i+1的位置起找真实最小值下标,如果计算的真实最小值下标与i不等,互换元素
void mySort(int arr[] , int len)
{
for (int i = 0; i < len;i++)
{
int min = i; //记录最小值的下标为i
for (int j = i + 1; j < len;j++)
{
if (arr[min]> arr[j])
{
//更新真实最小值下标
min = j;
}
}
//判断真实最小值下标 是否与开始认定的i相等,如果不等,交换元素
if (i != min)
{
int temp = arr[i];
arr[i] = arr[min];
arr[min] = temp;
}
}
}
采用选择排序对字符串数组排序时,字符串大小对比用strcmp函数
7.操作技巧
1.快捷导入代码:
VS --》 工具–》 代码片段管理器 --》 Visual C++ 类似与代码补全,用Tab键触发
2.传入main函数参数
无参main函数: int main(void) == int main()
带参数的main函数: int main(int argc, char *argv[]) == int main(int argc, char **argv)
参1:表示给main函数传递的参数的总个数。
参2:是一个数组!数组的每一个元素都是字符串 char *
测试1:
命令行中的中,使用gcc编译生成 可执行文件,如: test.exe
test.exe abc xyz zhangsan nichousha
-->
argc — 5
test.exe – argv[0]
abc – argv[1]
xyz – argv[2]
zhangsan – argv[3]
nichousha – argv[4]
测试2:
在VS中。项目名称上 --》右键–》属性–》调试–》命令行参数 --》将 test.exe abc xyz zhangsan nichousha 写入。
-->
argc — 5
test.exe – argv[0]
abc – argv[1]
xyz – argv[2]
zhangsan – argv[3]
nichousha – argv[4]
8.static修饰的全局,局部变量和函数
局部变量:
概念:定义在函数 内 部的变量。
作用域:从定义位置开始,到包裹该变量的第一个右大括号结束。
全局变量:
概念:定义在函数 外 部的变量。
作用域:从定义位置开始,默认到本文件内部。 其他文件如果想使用,可以通过声明方式将作用域导出。
static全局变量:
定义语法: 在全局变量定义之前添加 static 关键字。 static int a = 10;
作用域:被限制在本文件内部,不允许通过声明导出到其他文件。
static局部变量:
定义语法: 在局部变量定义之前添加 static 关键字。
特性: 静态局部变量只定义一次。在全局位置。 通常用来做计数器。
作用域:从定义位置开始,程序启动开始,程序终止结束。
全局函数: 函数
定义语法: 函数原型 + 函数体
static函数:
定义语法:static + 函数原型 + 函数体
static 函数 只能在 本文件内部使用。 其他文件即使声明也无效。
生命周期:
局部变量:从变量定义开始,函数调用完成。 --- 函数内部。
全局变量:程序启动开始,程序终止结束。 --- 程序执行期间。
static局部变量:程序启动开始,程序终止结束。 --- 程序执行期间。
static全局变量:程序启动开始,程序终止结束。 --- 程序执行期间。
全局函数:程序启动开始,程序终止结束。 --- 程序执行期间。
static函数:程序启动开始,程序终止结束。 --- 程序执行期间。
9.指针
1.野指针,空指针,万能指针
野指针:
1) 没有一个有效的地址空间的指针。
int *p;
*p = 1000;
-
p变量有一个值,但该值不是可访问的内存区域。
int *p = 10;
*p = 2000;
【杜绝野指针】
空指针:
int *p = NULL; #define NULL ((void *)0)
*p 时 p所对应的存储空间一定是一个 无效的访问区域。
万能指针/泛型指针(void *):
可以接收任意一种变量地址。但是,在使用【必须】借助“强转”具体化数据类型。
char ch = 'R';
void *p; // 万能指针、泛型指针
p = &ch;
printf("%c\n", *(char *)p);
1.空指针和野指针
空指针 Null
野指针的产生
-
指针变量未初始化
-
指针释放后未置空
-
指针操作超越变量作用域 如函数内定义指针并返回
-
野指针不能重复释放,空指针可以重复释放
//指针易错点
char * p = malloc(sizeof(char)* 64);
char * pp = p; //通过创建临时指针操作内存,防止出错
for (int i = 0; i < 10;i++)
{
*pp = i + 97;
printf("%c\n", *pp);
pp++; //更改指针位置,释放出错
}if (p!= NULL)
{
free§;
}
2.const修饰的指针
const int *p 可以修改 p 不可以修改 *p。
int const *p 同上。
int * const p 可以修改 *p 不可以修改 p。
const int *const p 不可以修改 p。不可以修改 *p。
总结:const 向右修饰,被修饰的部分即为只读。
常用:在函数形参内,用来限制指针所对应的内存空间为只读。即const用来修饰形参防止误操作
3.指针和数组的区别
-
指针是变量。数组名为常量 ===》不可被赋值。
-
sizeof(指针) ===》 4字节 / 8字节
sizeof(数组) ===》 数组的实际字节数。
4.指针和函数
栈 帧:
当函数调用时,系统会在 stack 空间上申请一块内存区域,用来供函数调用,主要存放 形参 和 局部变量(定义在函数内部)。
当函数调用结束,这块内存区域自动被释放(消失)。
传值和传址:
传值:函数调用期间,实参将自己的值,拷贝一份给形参。
传址:函数调用期间,实参将地址值,拷贝一份给形参。 【重点】
(地址值 --》 在swap函数栈帧内部,修改了main函数栈帧内部的局部变量值)
指针做函数参数:
int swap2(int *a, int *b);
int swap2(char *a, char *b);
调用时,传有效的地址值。
数组做函数参数:
void BubbleSort(int arr[10]) == void BubbleSort(int arr[]) == void BubbleSort(int *arr)
传递不再是整个数组,而是数组的首地址(一个指针)。
所以,当整型数组做函数参数时,我们通常在函数定义中,封装2个参数。一个表数组首地址,一个表元素个数。(此时无法通过sizeof 获得数组元素个数)
指针做函数返回值:
int *test_func(int a, int b);
指针做函数返回值,不能返回【局部变量的地址值】。
数组做函数返回值:
C语言,不允许!!!! 只能写成指针形式。
若将指针传入函数,可以通过指针修改指针指向的值,但是不能修改指针本身,只是对形参进行了简单的修改,
若想通过函数修改指针的值,需要传入指针的指针。
5. 指针的输入输出特性
指针的输入特性,主调函数中分配内存(分配在栈或堆上),被调函数使用
指针的输出特性,被调函数中分配内存(需要二级指针),主调函数使用
个人理解:将变量传入函数,只是将值传入形参,而形参是临时的,函数结束后销毁,实际的实参值没有改变
二级指针做函数的输入特性 文件 二级指针做函数的输入特性.c
- 创建在堆区,只需free二级指针名字,然后令其指向NULL
- 创建在栈区,需要free二级指针中的所有一级指针,分别令其指向NULL,不用free二级指针名字,因为是在栈区创建的
二级指针做函数的输出特性 见文件 二级指针做函数的输出特性.c
- 同级指针传入函数,只能释放指向的内容,不能将指针置NULL。
6.指针和字符串
指针和字符串:
1
char str1[] = {‘h’, ‘i’, ‘\0’}; 变量,可读可写
char str2[] = “hi”; 变量,可读可写
char *str3 = “hi”; 常量,只读
str3变量中,存储的是字符串常量“hi”中首个字符‘h’的地址值。
str3[1] = ‘H’; // 错误!!
char *str4 = {‘h’, ‘i’, ‘\0’}; // 错误!!!
2
当字符串(字符数组), 做函数参数时, 不需要提供2个参数。 因为每个字符串都有 ‘\0’。
7.指针的步长
- 指针的步长,代表指针+1后跳跃的字节数
- 在解引用的时候,解出的字节数量
可以通过强制类型转换,修改指针的步长
8.offsetof
offsetof(struct Person, d) 求struct中,d元素的偏移量。 头文件 stddef.h
9.字符串做指针
//字符串结束标志位 \0
char str1[] = { 'h', 'e', 'l', 'l', 'o'}; // 无结束标志位,打印失败
char str1[] = { 'h', 'e', 'l', 'l', 'o' ,'\0'};
printf("%s\n", str1);
char str2[100] = { 'h', 'e', 'l', 'l', 'o' }; // 其余位置全为'\0',打印成功
printf("%s\n", str2);
char str3[] = "hello"; //默认加'\0'
printf("%s\n", str3);
printf("sizeof str:%d\n", sizeof(str3)); //6
printf("strlen str:%d\n", strlen(str3)); //5
char str4[100] = "hello";
printf("sizeof str:%d\n", sizeof(str4)); //100
printf("strlen str:%d\n", strlen(str4)); //5
char str5[] = "hello\0world";
printf("%s\n", str5);
printf("sizeof str5:%d\n", sizeof(str5)); //12
printf("strlen str5:%d\n", strlen(str5)); //5
char str6[] = "hello\012world"; // '\012'(八进制) => '\10' => 回车
printf("%s\n", str6);
printf("sizeof str6:%d\n", sizeof(str6)); //12
printf("strlen str6:%d\n", strlen(str6)); //11
10.字符串拷贝,翻转,Sprintf
1、利用[] 实现
void copyString01(char * dest , char * src)
{
int len =strlen(src);
for (int i = 0; i < len;i++)
{
dest[i] = src[i];
}
dest[len] = '\0';
`}
2、利用字符串指针
void copyString02(char * dest, char * src)
{
while (*src != '\0')
{
*dest = *src;void copyString02(char * dest, char * src)
{
while (*src != '\0')
{
*dest = *src; dest++;
src++;
}
*dest = '\0';
}
3
void copyString03(char * dest, char * src)
{
while (*dest++ = *src++){}
}
void test02()
{
char * str = "hello world";char buf[1024];
//copyString01(buf, str);
//copyString02(buf, str);
copyString03(buf, str);
printf("%s\n", buf);
}
字符串翻转
void reverseString01(char * str)
{
//利用[]
int len = strlen(str);
int start = 0;
int end = len - 1;
while (start < end)
{
char temp = str[start];
str[start] = str[end];
str[end] = temp;
start++;
end--;
}
}
void reverseString02(char * str)
{
int len = strlen(str);
char * start = str;
char * end = str + len - 1;
while (start < end)
{
char temp = *start;
*start = *end;
*end = temp;
start++;
end--;
}
}
sprintf的使用
//1. 格式化
char buf[1024];
memset(buf, 0, 1024);
sprintf(buf, "今天 %d 年 %d月 %d日", 2018, 6, 30);
printf("%s\n", buf);
//2. 拼接字符串
memset(buf, 0, 1024);
char str1[] = "hello";
char str2[] = "world";
int len = sprintf(buf, "%s%s", str1, str2); //返回值是字符串长度 不包含\0
printf("buf:%s len:%d\n", buf, len);
//3. 数字转字符串
memset(buf, 0, 1024);
int num = 100;
sprintf(buf, "%d", num);
printf("buf:%s\n", buf);
int num = 100;
//设置宽度 右对齐
memset(buf, 0, 1024);
sprintf(buf, "%8d", num);
printf("buf:%s\n", buf);
设置宽度 左对齐
memset(buf, 0, 1024);
sprintf(buf, "%-8d", num);
printf("buf:%sa\n", buf);
//转成16进制字符串 小写
memset(buf, 0, 1024);
sprintf(buf, "0x%x", num);
printf("buf:%s\n", buf);
//转成8进制字符串
memset(buf, 0, 1024);
sprintf(buf, "0%o", num);
printf("buf:%s\n", buf);
11.函数指针
1.函数指针和指针函数
void func(int a ,char c)
{
printf("hello world\n");
}
//函数指针的定义
void test01()
{
//1、先定义出函数类型,再通过类型定义函数指针
typedef void(FUNC_TYPE)(int, char);
FUNC_TYPE * pFunc = func;
//pFunc(10, 'a');
//2、定义出函数指针类型,通过类型定义函数指针变量
typedef void( * FUNC_TYPE2)(int, char);
FUNC_TYPE2 pFunc2 = func;
//pFunc2(20, 'b');
//3、直接定义函数指针变量
void(*pFunc3)(int, char) = func;
pFunc3(30, 'c');
//函数指针 和 指针函数 区别?
//函数指针 指向了函数的指针
//指针函数 函数返回值是指针的函数
}
//函数指针的数组
void func1()
{
printf("func1 调用了\n");
}
void func2()
{
printf("func2 调用了\n");
}
void func3()
{
printf("func3 调用了\n");
}
void test02()
{
void(*pArray[3])();
pArray[0] = func1;
pArray[1] = func2;
pArray[2] = func3;
for (int i = 0; i < 3;i++)
{
pArray[i]();
}
}
2.函数指针做函数参数(回调函数)
//提供一个打印函数,可以打印任意类型的数据
void printText( void * data , void(*myPrint)(void *) )
{
myPrint(data);
}
void myPrintInt(void * data)
{
int * num = data;
printf("%d\n", *num);
}
void test01()
{
int a = 10;
printText(&a, myPrintInt);
}
struct Person
{
char name[64];
int age;
};
void myPrintPerson(void * data)
{
struct Person * p = data;
printf("姓名: %s 年龄: %d\n", p->name, p->age);
}
void test02()
{
struct Person p = { "Tom", 18 };
printText(&p, myPrintPerson);
}
//提供一个打印函数,可以打印任意类型的数组
void printAllArray(void * pArray , int eleSize, int len , void(*myPrint)(void*) )
{
char * p = pArray;
for (int i = 0; i < len;i++)
{
//获取数组中每个元素的首地址
char * eleAddr = p + eleSize * i;
//printf("%d\n", *(int *)eleAddr);
//交还给用户做打印操作
myPrint(eleAddr);
}
}
void myPrintInt(void * data)
{
int * num = data;
printf("%d\n", *num);
}
struct Person
{
char name[64];
int age;
};
void myPrintperson(void * data)
{
struct Person * p = data;
printf("姓名:%s 年龄:%d \n", p->name, p->age);
}
void test01()
{
int arr[5] = { 1, 2, 3, 4, 5 };
int len = sizeof(arr) / sizeof(int);
printAllArray(arr, sizeof(int), len, myPrintInt);
}
void test02()
{
struct Person personArray[] =
{
{ "aaa", 10 },
{ "bbb", 20 },
{ "ccc", 30 },
{ "ddd", 40 },
};
int len = sizeof(personArray) / sizeof(struct Person);
printAllArray(personArray, sizeof(struct Person), len, myPrintperson);
}
//回调函数提供的查找功能
int findArrayEle(void * pArray, int eleSize, int len, void * data , int(*myCompare)(void* ,void* ) )
{
char * p = pArray;
for (int i = 0; i < len;i++)
{
//每个元素的首地址
char * eleAddr = p + eleSize * i;
//if ( 数组中的变量的元素 == 用户传入的元素)
if ( myCompare(eleAddr,data) )
{
return 1;
}
}
return 0;
}
int myComparePerson(void * data1,void * data2)
{
struct Person * p1 = data1;
struct Person * p2 = data2;
//if ( strcmp( p1->name , p2->name) == 0 && p1->age == p2->age)
//{
// return 1;
//}
//return 0;
return strcmp(p1->name, p2->name) == 0 && p1->age == p2->age;
}