01 C

1、C语言编译环境

1.1 notepad++

可以使用notepad++,进行C文件的编写,习惯会更接近于Linux编写。

先创建.c文件,然后放到notepad++编写即可。

在命令行使用gcc进行编译,要先离线安装mingw,在线安装很容易失败。

mingw解压之后,直接在命令行运行gcc命令是不行的,要先进到mingw64/bin这个文件夹中才行,那么将这个路径添加到(此电脑-属性-高级系统设置-环境变量-系统变量-Path-新建)之后,在打开终端之后就可以直接使用gcc命令了。

要编译的时候,使用cd命令跳到指定代码文件夹

bash 复制代码
#切换目录
cd /d E:    #切换到E盘了

然后进行编译和运行

bash 复制代码
gcc test.c    #编译test.c文件,会生成一个a.exe文件
gcc test.c -o b    #把生成的文件名改成b.exe
a.exe    #运行test程序产生的结果

如果代码中有输出中有中文,可以在notepad++的编码中选择编码字符集-中文-GB2312(简体中文),然后重新编译就可以解决中文乱码问题

2、指针相关

2.1 什么是指针

指针就是地址,*是取地址运算符,使用*将地址内的值读出

指针变量就是存放地址的变量

2.2 *的作用

*的两种作用:标识作用,用于定义一个指针变量(只产生在指针变量定义或声明的时候);运算作用,去使用一个指针变量。

2.3 指针偏移

指针+1,具体加多少和指针的类型有关。

指针变量可以自增,指针常量不可以。

cpp 复制代码
int main()
{
    int arr[3] = {1,2,3};
    int *p = arr;

    //这里用偏移的方式访问
    for(int i = 0; i<3; i++){
        printf("%d ",*(arr+i));
    }

    //这里p是指针变量,可以自增
    for(int i = 0; i<3; i++){
        printf("%d ",*p++);
    }

    //这里arr是指针常量,不可以自增
    for(int i = 0; i<3; i++){
        printf("%d ",*arr++);
    }
    return 0;
}

2.4 二维数组 野指针 数组指针 函数指针 指针数组 指针函数 二级指针

二维数组,本质上是个特殊的一维数组。C语言规定数组名代表数组首元素的地址,那么一个3x4的二维数组a可以表示为a[b1[4],b2[4],b3[4]],即a是有3个b的一维数组,而b是有4个元素的一维数组。a是整个二维数组的地址,a[0]可以理解为是a中的子数组名(子数组首地址),&(a+1)表示从b1到b2,&(a[o]+1)表示从b1[0]到b1[1],而*(a+1)表示的意思与&(a[o]+1)相同,因为当用指针时,操作的就是整个数组的首地址,不会区分是一维还是二维。

野指针,没有明确的内存指向,很危险。对野指针的内存空间进行操作是不被允许的。而悬空指针是一种特殊的野指针,C语言中的指针可以指向一块内存,如果这块内存稍后被操作系统回收(被释放),但是指针仍然指向这块内存,那么此时该指针就是"悬空指针"。在释放内存后,使用p=NULL,可以防止悬空指针。

cpp 复制代码
char *p;//野指针

*p = 'a';

数组指针,是定义一个指针,指向一个数组。数组指针才是真正等同于二维数组名。

函数指针,

cpp 复制代码
int getData(int a, int b);
int (*p)(int a, int b);    //*的优先级要低于(),所以定义函数指针要把*p用()括起来
int *p(int a, int b);    //表示p(int a, int b)这个函数返回的是一个地址

//使用函数指针
void printWelcome()
{
    puts("程序启动,欢迎使用\n");
}

int main()
{
    void (*p)();//定义一个函数指针
    p = printWelcome;//指向函数
    printWelcome();//使用变量名(函数名)直接调用
    (*p)();//使用函数指针间接调用
    return 0;
}

指针数组,强调的是数组,即一个数组,其元素均为指针类型,也就是说该数组的每一个元素都存放一个地址,相当于一个指针变量。

cpp 复制代码
int * p[4];//定义一个指针数组,[]比*优先级更高,所以p先和[4]结合
int (* p)[4];//这是数组指针,指向一维数组的指针变量

指针函数,返回指针值的函数。

cpp 复制代码
int *p;    //指针变量,
int* p();    //变量函数,返回值是指针,返回的指针指向整型变量

二级指针(多级指针), 理解可以当作一级指针逐步进行。二级指针不能简单粗暴的指向二维数组。如果保存的地址是指针变量地址,那么就需要用二级指针来保存,而不能用一级指针。

cpp 复制代码
int data = 100;
int *p = &data;
printf("data的地址:%p\n",&data);
printf("p保存data的地址:%p\n,内容是%d\n",p,*p);

int **p2;
p2 = &p;
printf("p2保存p的地址:%p\n",p2);
printf("p2是:%p\n",*p2);
printf("**p2来访问data:%d\n",**p2);

//

int main()
{
    int scores[3][4]={
        {55,66,77,88},
        {66,55,99,100},
        {11,22,33,59},
    };//int (*p)[4];
    int (*p2)[4] = scores;

/*
    int **p;
    p = scores;//会出问题!
    printf("scores:%p\n",scores);
    printf("p=%p\n",p);
    printf("*p=%p\n",*p);//*p是一个野指针,不是我们认为的会变成列地址
    printf("*score=%p\n",*scores);//*p是一个野指针,不是我们认为的会变成列地址

    **p = 100;
    printf("done\n");*/

    int **p3 = &p2;//能用
    **p3 = 100;//能改的动,但是一般不这么进行
    printf("%d\n",scores[0][0]);
    return 0;
}

2.5 指针练习题

cpp 复制代码
1、一个整形数: 
2、一个指向整形数的指针: 
3、一个指向指针的指针,它指向的指针指向一个整形数: 
4、一个有10个整形数的数组: 
5、一个有10个指针的数组,每个指针指向一个整形数: 
6、一个指向有10个整形数的数组的指针: 
7、一个指向指针的指针,被指向的指针指向一个有10个整形数的数组: 
8、一个指向数组的指针,该数组有10个整形指针: 
9、一个指向函数的指针,该函数有一个整形参数并返回一个整形数: 
10、一个有10个指针的数组,每个指针指向一个函数,该函数有一个整形参数并返回一个整形数:
11、一个函数的指针,指向的函数的类型是有两个整形参数并且返回一个函数指针的函数,返回的函教指针指向有一个整形参数且返回整形数的函数: 

1、int a;
2、int *a;
3、int **a;
4、int a[10];
5、int *a[10];
6、int (*a)[10];
7、int (**a)[10];
8、int *(*a)[10];
9、int (*a)(int);
10、int (*a[10])(int);
11、int (*(*a)(int,int))(int);

3、字符串相关

3.1 定义字符串

cpp 复制代码
char data[] = "hello";//使用数组的方式定义,这是变量
char *pchar = "hello";//使用指针定义,这是字符串常量,不允许被修改


data[3] = 'm';//允许
*pchar = 'm';//不允许

3.2 字符串和字符数组的区别

cpp 复制代码
char cdata[] = {'h','e','l','l','o'};//这不是一个标准的字符串概念,只是用一个数组的方式表现出来
char cdata2[] = "hello";


int len = sizeof(cdata)/sizeof(cdata[0]);
printf("len= %d\n",len);//len=5

int len = sizeof(cdata2)/sizeof(cdata2[0]);
printf("len= %d\n",len);//len=6,因为字符串默认还有一个字符串结束标志'\0'

3.3 sizeof和strlen的区别

sizeof是计算分配区域的大小,strlen是计算字符串的有效长度。

cpp 复制代码
#include <stdio.h>
#include <string.h>



int main()
{
	char cdata[] = "hello";
	printf("sizeof: %d\n", sizeof(cdata));	//len=6
	printf("strlen: %d\n", strlen(cdata));	//len=5
	
	char *p = "hello";
	printf("sizeof: %d\n", sizeof(p));	//len=8,p是一个char *型的指针,用sizeof计算得出的是计算机用多少字节来表示一个地址
	printf("strlen: %d\n", strlen(p));	//len=5
	
	return 0;
}

3.4 动态开辟字符串

malloc,函数原型是void *malloc(size_t size),代表由C库函数void *malloc(size_t size)分配所需的内存空间,并返回一个指向它的指针。使用这个函数需要在头文件包含stdlib.h这个库。

realloc,函数原型是void *realloc(void *ptr,size_t size),用于扩容,代表C库函数void *realloc(void *ptr,size_t size)尝试重新调整之前调用malloc或者calloc所分配的ptr所指向的内存块的大小。

free,如果用malloc、calloc、realloc等开辟了内存,那么在使用完之后要通过free释放内存 ,防止内存泄漏。函数原型是void free(void *ptr)。

cpp 复制代码
void *p = malloc(size);
free(p); 
// 避免"悬空指针"
p = NULL;

memset,函数原型是void *memset(void *str, int c, size_t n),代表复制字符 c(一个无符号字符)到参数 str 所指向的字符串的前 n 个字符。可以对较大的结构体或数组进行快速清零操作。

3.5 字符串常用api

输出字符串:puts()、printf("%s\n",p);

获取字符串:scanf("%s", str)、gets(str);但是因为gets函数可以无限读取,易发生溢出。如果溢出,多出来的字符将被写入到堆栈中,这就覆盖了堆栈原先的内容,会破坏一个或多个不相关变量的值。

计算字符串长度strlen,函数原型是size_t strlen(const char *str) 计算字符串 str 的长度,直到空结束字符,但不包括空结束字符。

拷贝字符串:strcpy,函数原型是char *strcpy(char *dest, const char *src) 把 src 所指向的字符串复制到dest。strncpy,函数原型是char *strncpy(char *dest, const char *src, size_t n) ,把src 所指向的字符串复制到dest,最多复制 n 个字符。当 src 的长度小于 n 时,dest 的剩余部分将用空字节填充。

字符串拼接:strcat,函数原型是 char *strcat(char *dest, const char *src) 把 src 所指向的字符串追加到 dest 所指向的字符串的结尾。要保证dest的空间足够

字符串比较:strcmp,函数原型是 int strcmp(const char *str1, const char *str2) 把 str1 所指向的字符串和 str2 所指向的字符串进行比较。如果 str1 小于 str2,那么返回值小于 0;如果 str1 等于 str2,那么返回值等于 0;如果 str1 大于 str2,那么返回值大于 0。

strncmp,函数原型是 int strncmp(const char *str1, const char *str2, size_t n) 把 str1 和 str2 进行比较,最多比较前 n 个字符。返回值于strcmp相同。

查找子字符:strchr,函数原型是 char *strchr(const char *str, int c) ,在参数 str 所指向的字符串中搜索第一次出现字符 c(一个无符号字符)的位置。如果在字符串 str 中找到字符 c,则函数返回指向该字符的指针,如果未找到该字符则返回 NULL。

查找子串:strstr,函数原型是 char *strstr(const char *haystack, const char *needle)在字符串 haystack 中查找第一次出现字符串 needle 的位置,不包含终止符 '\0'。该函数返回在 haystack 中第一次出现 needle 字符串的位置,如果未找到则返回 null。

字符串分割:strtok,函数原型是 char *strtok(char *str, const char *delim) 分解字符串 str 为一组字符串,delim 为分隔符。该函数返回被分解的第一个子字符串,如果没有可检索的字符串,则返回一个空指针。特别要注意的是分割处理后原字符串会str会改变,原字符串的改动是切分符原位置均更改为'\0'。

断言:assert,函数原型是voidassert(int expression) ,允许诊断信息被写入到标准错误文件中。换句话说,它可用于在 C 程序中添加诊断。expression -- 这可以是一个变量或任何 C 表达式。如果 expression 为 TRUE,assert() 不执行任何动作。如果 expression 为 FALSE,assert() 会在标准错误 stderr 上显示错误消息,并中止程序执行。

cpp 复制代码
#include <stdio.h>

int main()
{
	char *p = "hello";
	char str[128] = {'\0'};
	
	//输出字符串
	puts(p);	//使用puts输出,自带换行符
	printf("%s\n",p);
	
	//获取字符串
	scanf("%s", str);
	puts(str);
	gets(str);
	puts(str);
	
	return 0;
}



int main ()
{
   const char str[] = "https://www.runoob.com";
   const char ch = 'o';
   char *ptr;

   ptr = strchr(str, ch);

    if (ptr != NULL) {
        printf("字符 'o' 出现的位置为 %ld。\n", ptr - str + 1);    //字符 'o' 出现的位置为 16
        printf("|%c| 之后的字符串是 - |%s|\n", ch, ptr);    //|o| 之后的字符串是 - |oob.com|
    } else {
        printf("没有找到字符 'o' 。\n");
    }
   return(0);
}



int main()
{
   const char haystack[20] = "RUNOOB";
   const char needle[10] = "NOOB";
   char *ret;
 
   ret = strstr(haystack, needle);
 
   printf("子字符串是: %s\n", ret);    //子字符串是: NOOB
   
   return(0);
}



int main () {
   char str[80] = "This is - www.runoob.com - website";
   const char s[2] = "-";
   char *token;
   
   /* 获取第一个子字符串 */
   token = strtok(str, s);
   
   /* 继续获取其他的子字符串 */
   while( token != NULL ) {
      printf( "%s\n", token );    //www.runoob.com ;website
    
      token = strtok(NULL, s);
   }
   
   return(0);
}

4、结构体

4.1 结构体的定义

如果需要将不同元素类型的数据集合在一起,那么就需要用到结构体了。

cpp 复制代码
//结构体模版,一般不给赋具体的值,每一项在实际应用中并不是都要使用
//每个成员都是结构体中的一个域,成员列表,也成为域表

struct Student	//struct告知系统是一个结构体
{
	int num;
	char name[32];
	char sex;
	int age;
	double score;
	char addr[32];
};	//分号不能忘


struct Student stu1;    //结构体变量声明


struct Student	
{
	int num;
	char name[32];
	char sex;
	int age;
	double score;
	char addr[32];
} stu1,stu2;	    //在声明结构体的同时声明了两个结构体变量,实际当中最好不要这样用

4.2 结构体的使用

使用 . 来访问结构体中的成员。

4.3 结构体数组

cpp 复制代码
struct Student	//struct告知系统是一个结构体
{
	int num;
	char name[32];
	char sex;
	int age;
	double score;
	char addr[32];
};	


int main()
{
	struct Student arr2[3] = {
		{2, "张三", 'm', 17, 99,5, "北京"},
		{3, "李四", 'm', 18, 97,5, "上海"},
		{4, "小红", 'g', 17, 99,9, "广州"},
	};
	len = sizeof(arr2)/sizeof(arr2[0]);
	
	for(i = 0; i < len; i++){
		printf("学号:%d, 姓名:%s, 年龄: %d,分数:%lf,地址:%s\n ",
			arr2[i].num, arr2[i].name, arr2[i].age, arr2[i].score, arr2[i].addr);
	}
	
	return 0;
}

4.4 结构体指针

使用结构体指针时,要把以前的普通变量名,或者下标访问的 . 运算符,改成结构体指针的->;指针++之后,每次遍历会到数组尾巴,下次遍历之前记得要回来(重新指向数组头)。

cpp 复制代码
#include <stdio.h>

struct Test
{
	int idata;
	char cdata;
};

int main()
{
	int a = 10;
	int *p = &a;
	
	char c = 'c';
	char *pc = &c;
	
	struct Test t1 = {10, 'c'};
	struct Test *ps = &t1;

	printf("t1的idata=%d\n", t1.idata);//变量名访问,用.运算符
	printf("t1的idata=%d\n", ps->idata);//指针变量名访问,用->运算符
	
	return 0;
}

5、共用体(联合体)

5.1 共用体的定义

可以存储不同的数据类型,与结构体不同的是,这些不同的数据类型共用一个存储空间,空间大小由最大类型确定,且任何时候都只能由1个成员带值。

cpp 复制代码
//共用体模版,一般不给赋具体的值,每一项在实际应用中并不是都要使用
//每个成员都是共用体中的一个域,成员列表,也成为域表

union Student	//union 告知系统是一个共用体
{
	int num;
	char name[32];
	char sex;
	int age;
	double score;
	char addr[32];
};	//分号不能忘


union Student stu1;    //共用体变量声明


union Student	
{
	int num;
	char name[32];
	char sex;
	int age;
	double score;
	char addr[32];
} stu1,stu2;	    //在声明共用体的同时声明了两个共用体变量,实际当中最好不要这样用

5.2 共用体的使用

访问共用体成员与结构体类似。

cpp 复制代码
#include <stdio.h>

struct Person
{
	char name[32];
	int age;
	char zhiYe;
	char addr[];
	union D{
		int class;
		char keMu[12];
	}mes;
};

int main()
{
	struct Person p[2];
	int i;
	for(i = 0; i < 2; i++){
		printf("请输入职业:t代表老师,s代表学生\n");
		scanf("%s", &(p[i].zhiYe));
		if(p[i].zhiYe == 'c'){
			printf("请输入学生班级:\n");
			scanf("%d", &(p[i].mes.class));
		}
		else{
			printf("请输入老师的科目:\n");
			scanf("%s", p[i].mes.keMu);
		}
        getchar();    //处理输入数据之后的回车被scanf("%s", &(p[i].zhiYe));吸收的问题
	}
	
	return 0;
}

6、枚举

6.1 枚举的定义

用于定义一组具有离散值的常量,它可以让数据更简洁,更易读。枚举类型通常用于为程序中的一组相关的常量取名字,以便于程序的可读性和维护性。

cpp 复制代码
//对一周七天的定义
#define MON  1
#define TUE  2
#define WED  3
#define THU  4
#define FRI  5
#define SAT  6
#define SUN  7

//使用枚举可以将上面改写为
//声明枚举类型
enum Weekday
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};    //第一个枚举成员的默认值为整型的 0,后续枚举成员的值在前一个成员上加 1。在这个实例中把第一个枚举成员的值定义为 1,第二个就为 2,以此类推。


//定义枚举变量方法一:先定义枚举类型,再定义枚举变量
enum Weekday day;


//定义枚举变量方法二:定义枚举类型的同时定义枚举变量
enum Weekday
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;


//定义枚举变量方法三:省略枚举名称,直接定义枚举变量
enum
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
} day;

6.2 枚举的使用

不同的枚举当中,变量名不能相同。

在C 语言中,枚举类型是被当做 int 或者 unsigned int 类型来处理的。

当枚举类型连续时,可以遍历枚举。不连续时,则不行。

获取枚举中某个值,可以直接通过变量名访问。

cpp 复制代码
#include <stdio.h>
 
enum DAY
{
      MON=1, TUE, WED, THU, FRI, SAT, SUN
};
 
int main()
{
    enum DAY day;
    day = WED;
    printf("%d",day);

    // 遍历枚举元素
    for (day = MON; day <= SUN; day++) {
        printf("枚举元素:%d \n", day);
    }

    return 0;
}

//以下枚举类型不连续,无法完成遍历
enum
{
    ENUM_0,
    ENUM_10 = 10,
    ENUM_11
};

7、typedef

7.1 typedef的定义

可以使用它来为已有类型取一个新的名字。

cpp 复制代码
typedef unsigned char BYTE;

8、注意事项

在形参中,不存在数组的概念,即便中括号中约定了数组的大小,也无效。

cpp 复制代码
void printf(int arry[10])//形参中不存在数组的概念,即使中括号中约定了数组的大小,也无效
                           //这里传递的是一个地址,是数组的首地址
{
    int i;
    printf("printArr:arry的大小是:%d\n",sizeof(array));//这里用8个字节来表示一个地址
    for(i=0;i<10;i++)
    {
        printf("%d ",arry[i]);
    }
    putchar('\n');
}
相关推荐
励志成为嵌入式工程师3 小时前
c语言简单编程练习9
c语言·开发语言·算法·vim
Peter_chq4 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
hikktn5 小时前
如何在 Rust 中实现内存安全:与 C/C++ 的对比分析
c语言·安全·rust
观音山保我别报错5 小时前
C语言扫雷小游戏
c语言·开发语言·算法
小林熬夜学编程8 小时前
【Linux系统编程】第四十一弹---线程深度解析:从地址空间到多线程实践
linux·c语言·开发语言·c++·算法
墨墨祺8 小时前
嵌入式之C语言(基础篇)
c语言·开发语言
躺不平的理查德8 小时前
数据结构-链表【chapter1】【c语言版】
c语言·开发语言·数据结构·链表·visual studio
幼儿园园霸柒柒9 小时前
第七章: 7.3求一个3*3的整型矩阵对角线元素之和
c语言·c++·算法·矩阵·c#·1024程序员节
好想有猫猫10 小时前
【51单片机】串口通信原理 + 使用
c语言·单片机·嵌入式硬件·51单片机·1024程序员节
摆烂小白敲代码10 小时前
背包九讲——背包问题求方案数
c语言·c++·算法·背包问题·背包问题求方案数