c语言笔记第一部分

记录一下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步骤:

  1. 预处理 -E xxx.i 预处理文件

    gcc -E xxx.c -o xxx.i

    1. 头文件展开。 — 不检查语法错误。 可以展开任意文件。

    2)宏定义替换。 — 将宏名替换为宏值。

    3)替换注释。 — 变成空行

    4)展开条件编译 — 根据条件来展开指令。

  2. 编译 -S xxx.s 汇编文件

    gcc -S hello.i -o hello.s

    1)逐行检查语法错误。【重点】 — 整个编译4步骤中最耗时的过程。

    2)将C程序翻译成 汇编指令,得到.s 汇编文件。

  3. 汇编 -c xxx.o 目标文件

    gcc -c hello.s -o hello.o

    1)翻译:将汇编指令翻译成对应的 二进制编码。

  4. 链接 无 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.一维数组名称

有两种特殊情况,一维数组名不是 指向第一个元素的指针

  1. sizeof

  2. 对数组名取地址 得到数组指针 步长是整个数组长度

    //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;

  1. 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

野指针的产生

  1. 指针变量未初始化

  2. 指针释放后未置空

  3. 指针操作超越变量作用域 如函数内定义指针并返回

  4. 野指针不能重复释放,空指针可以重复释放

    //指针易错点
    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.指针和数组的区别

  1. 指针是变量。数组名为常量 ===》不可被赋值。

  2. 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

  1. 创建在堆区,只需free二级指针名字,然后令其指向NULL
  2. 创建在栈区,需要free二级指针中的所有一级指针,分别令其指向NULL,不用free二级指针名字,因为是在栈区创建的

二级指针做函数的输出特性 见文件 二级指针做函数的输出特性.c

  1. 同级指针传入函数,只能释放指向的内容,不能将指针置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. 指针的步长,代表指针+1后跳跃的字节数
  2. 在解引用的时候,解出的字节数量

可以通过强制类型转换,修改指针的步长

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;

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值