C语言提高(1)

在C语言中,_CRT_SECURE_NO_WARNINGS 是一个宏定义,它通常与Microsoft Visual Studio(MSVC)编译器一起使用,用于禁用一些与安全相关的警告。这些警告通常与C标准库中的某些函数的安全性问题相关,特别是在处理字符串和内存时。
_CRT_SECURE_NO_WARNINGS 宏定义的作用是在编译时禁用与这些不安全函数使用相关的警告。通过在代码中的某个位置(通常在包含任何标准库头文件之前)定义这个宏,你可以告诉编译器忽略这些警告,从而避免在编译时看到大量的与安全相关的消息。

c 复制代码
#define _CRT_SECURE_NO_WARNINGS
#include <stdio.h>
#include <string.h>

数据类型

数据类型就是为了更好的内存管理


typede

c 复制代码
/**定义结构体**/
//方法一
struct Person 
{
	char name[64];
	int age;	
};
typedef struct Person myPerson;

//方法二
typedef struct Person
{
	char name[64];
	int age;
}myPerson;
c 复制代码
/**定义变量**/
//方法一,无法给两个变量都赋值为char *
void test()
{
	char * p1,p2;
	cout << typeid(p1).name << endl;//char *
	cout << typeid(p2).name << endl;//char
}
//方法二
typedef char * PCHAR
void test()
{
	PCHAR p1,p2;
	cout << typeid(p1).name << endl;//char *
	cout << typeid(p2).name << endl;//char *
}
c 复制代码
/**有利于程序移植性**/
typedef long long mytype_t;
void test() 
{
	mytype_t a;
}

void

void 无类型,void * 无类型指针,无类型指针可以指向任何类型的数据

void不能直接定义变量,因为编译器不知道分配多少内存给变量

当定义一个变量,编译器必须要直到分配多少内存,否则报错

c 复制代码
/**对函数负担会的限定顶,对函数参数的限定**/

void test()//函数返回值为void,即没有返回值 
{
	cout << "this is test" << endl;
}
//test1无参数
int test1(void)
{
	return 10;
}
c 复制代码
/**无类性指针**/
//所有类型指针的祖宗
//void * 主要用于数据结构的封装
void test() 
{
	void *p = NULL:	
	int *PInt = NULL;
	void *PVoid = PInt;//不报错
}

sizeof操作符

能够告诉我们编译器为某一特定数据分配了多少大小的空间和内存,大小一字节为单位

返回的是给变量分配的空间,而不是它所使用的空间

返回的数据结果类型是 unsigned int

c 复制代码
/**返回的是变量实际所占空间的大小,单位字节**/
#pragma pack(1)//让结构体的数据对齐
struct Person
{
	char a;
	int b;
}
void test()
{
	printf("int size:%d\n",sizeof(int));//int size:4
	double a = 3.14;
	printf("a size:%d\n",sizeof a);//a size:8
	printf("Person size:%d\n",sizeof(struct Person));// Person size:8未加对其魔术#pragma pack(1),加了之后的结果Person size:5
}
c 复制代码
/**sizeof返回结果为unsigned int**/
void test() //大于0,因为大部分编译器在编译无符号和有符号运算结果的时候一般结果都是无符号的,因此不会是小于0
{
	unsigned int a = 10;
	if(a - 20 > 0) printf("大于0");
	else printf("小于0");
}
c 复制代码
/**sizeof计算数组**/
//当我们使用它sizeof去求数组的大小时,数组传入函数里面时数组的大小已经不是原本数组的大小了,而大小是指向数组首元素的指针
int caculateArraySizer(int arr[]) 
{
	return sizeof(arr);
}
void test()
{
	int arr[] = {1,2,3,4,5,6,7};
	printf("sizeof arr:%d\n",sizeof(arr));//sizeof arr:28
	ptintf("sizeof arr:%d\n",caculateArraySizer(arr));//sizeof arr:4【大小退化为指向数组首元素的指针,指针大小为4个字节】
}

变量的间接赋值

c 复制代码
//首先拿到变量的地址,确保指针p指向的地址还是能用的
int *p = &a;
//在对指针p指向的地址也就是a进行赋值
*p= 20;
c 复制代码
struct Person 
{
	char a;
	int b;
	char c;
	int d;
};
void test() 
{
	struct Person p = {'a',100,'b',200};
	printf("p.d:%d\n",p.d);//p.d:200	
	p.d = 1000;//直接修改
	//间接修改
	printf("%d\n",*(int *)(char *)&p + 12)
	/**	
		首先使用取地址符&取出struct Person 的地址,取出的结果是一个struct Person 类型的指针
		其次因为指针数据只要加一就会跳转这个指针类型大小的位置,即int *p = NULL;p+1 = 8
		所以为了能够使Person类型的指针加1不会直接跳到结构体末尾,先将指针赋值未char *类型,此刻p.d所在的位置相对于起始指针差12个位置我们将指针进行偏移
		刺此刻指针指向p.d的开始位置,然后将指向p.d转为int * 类型就可以得到类型为int的指针
		此刻在对指针进行转换就得到指针所指向内存区域的数据,即加上*读取指针指向内存区域数据
	**/
}

内存分区

代码编译成可执行程序之后

代码区:只读的、共享的;存放CPU执行的指令

全局变、静态变量数据区:初始化、未初始化的的全局、静态变量
运行之后

代码区:只读的、共享的;存放CPU执行的指令

全局变量、静态变量数据区:初始化、未初始化的的全局、静态变量

堆区:大容器,容量远远大于栈,动态内存分配,堆在内存中位于BSS区很栈区之间,一般由程序员分配和释放,若程序员不释放,程序结束时由操作系统回收。需要存放的数据内存比较大一般选择放在堆上。

栈区:先进后出,由编译器自动分配释放,存放函数的参数值、返回值、局部变量。程序在运行过程中实时加载和释放,栈的大小时固定的系统自行释放栈区内存,不需要用户管理。

全局静态区:全局变量(全局区)、静态变量(静态区)、常量(常量区)

栈区

c 复制代码
int* myFunc()
{
	//不要返回局部变量的地址
	int a = 10;//存在于栈上,函数执行完毕内存自动回收
	return &a;
}
void test()
{
	//我们并不关心值是多少,因为局部变量a的内存已经被收回
	int* p = myFunc();
	printf("*p = %d\n",*p);
}
c 复制代码
char *getString()
{
	char str[] = "hello world";//数组str在栈上,但是"hello world"在常量区,因此当此函数运行结束的时候这个数组str也被释放,所以也不能访问str,但是"hello world"还存在
	return str;
}
void test() 
{
	char *s = getString();
	printf("s = %s\n",s);//打印出乱码
}

堆区

c 复制代码
/**堆的内存成员手动申请,手动释放**/
int *getSpace()
{
	int *p = malloc(sizeof(int) *5);//内存空间在堆上,函数执行完毕*p会释放,但是堆上的内存malloc(sizeof(int) *5)不会被释放
	if(NULL == p)
	{
		return NULL;
	}
	//只要是连续的内存空间,都能使用下表的方式访问内存
	for(int i = 0;i < 5;++i)
	{
		p[i] = 100+i;
	}
	return p;
}
void test()
{
	int *ret = getSpace();
	for(int i = 0;i < 5;++i)
	{
		printf("%d",ret[i]);
	}
	//用完堆内存记得释放
	free(ret);
	ret = NULL;
}
c 复制代码
/**定义变量的时候一定要初始化,很多BUG的产生都是由于没有初始化造成的**/
void allocateSpace(char *p)
{
	p = malloc(100);//创建堆空间
	memset(p,0,100);//初始化变量
	strcpy(p,"hello world");//拷贝常量区数据"hello world"到p里面
}
void test()
{
	char *p1 = NULL:
	alocateSpace(p1);
	printf("p = %s\n",p1);//p = NULL
}
/**
	因为在执行allocateSpace(char* p)函数时,
	会在栈区创建一个区域存储allocateSpace的参数char *p,
	因此这个参数是局部变量,
	在函数allocateSpace执行完毕之后参数 char *p就会杯释放,
	因此不会影响到test函数中char *p1的值

	如果要使程序正确可以让allocateSpace函数的参数变为char **p一个二级指针,
	并且在test中要将p1的地址传入进去
	将p1的地址存放在p的地址中
	这时候在allocateSpace中首先会用temp指向存放在堆上开辟的空间
	然后再将堆上的空间拷贝到指针*temp指向的内存
	最后再用指针*p指向temp指向的空间(代码如下所示)
**/
void allocateSpace(char **p)
{
	char *temp = malloc(100);//创建堆空间
	memset(temp,0,100);//初始化变量
	strcpy(temp,"hello world");//拷贝常量区数据"hello world"到p里面
	*p = temp;
}
void test()
{
	char *p1 = NULL:
	alocateSpace(&p1);
	printf("p = %s\n",p1);//p = hello world
}

全局静态区

c 复制代码
/**
	全局静态变量和局部静态变量都存储在静态区,都是程序运行期间合法有效
	局部静态变量符号的可见范围仅限于当前函数内部,全局静态变量可见范围从定义到文件结尾
**/
int a = 10;//全局区 相当于 extern int a = 10;默认外部链接
//静态全局变量使内部链接
static int b = 20;//静态区,全局静态变量
/**
	内部链接和外部链接有什么区别?
	如果变量使内部链接的话,那么此变量只能在当前文件内访问
	如果变量使外部链接大的话,那么此变量可以被其他文件使用
**/

void test()
{
	static int c = 30;//静态区,局部静态变量
}
/**
	头文件不参与编译,每一个.c文件,我们叫做一个编译单元
	编译器独立编译每一个.c文件
**/

void test2()
{
	//声明,表示告诉编译器这个符号使存在的,让我先通过编译,让链接器去找这个符号在哪
	extern int g_a;
	printf("g_a = %d\n",g_a);
} 
c 复制代码
//常量区 字符串常量 全局const常量
/**
	const全局和局部变量区别?
	const 全局变量在常量区,不能修改(直接或简介)
**/
const int g_a = 100;//全局静态变量
void test()
{
	//直接修改不行
	//g_a = 200;
	
	//间接修改不行
	//int *p = (int *)&g_a;
	//*p = 200;//编译能通过,但是不能执行
}
c 复制代码
//const局部变量
void test()
{
	//数据存储在栈上,存储在栈上的数据使可以被修改的
	const int a = 100;

	//不能直接修改
	//a = 20;

	//可以间接修改
	int *p = (int *)&a;
	*p = 200;
}

字符串常量区

c 复制代码
void test()
{
	//字符串hello world存在常量区,只读不可更改,但在不同编译器上会不一样
	char *p = "hello world";
	printf("%d\n",&"hello world");//1028632080
	printf("%d\n",p);//1028632080
}

ANSI规定:修改字符串常量,结果是未定义的(导致不同编译器对修改字符串由不同的结果)

ANSI并没有规定编译器的实现者对字符串的处理:

(1)有些编译器可以修改字符串常量,有些编译器则不能修改字符串常量

(1)有些编译器将多个相同字符串常量看成一个

函数调用

函数必须要有返回值、参数、函数体

一个函数调用过程中所需要的信息包含:函数返回地址、函数参数、临时变量、保存上下文,包括在函数调用前后需要保存不变的寄存器

c 复制代码
//宏函数,但是它不算是函数
//宏函数在一定场景下效率比函数高
#define  MYADD(x,y) ((x)+(y))
void test() 
{
	int a = 10;
	int b = 20;
	printf("a + b = %d\n",MYADD(a,b));
}

调用惯例

函数调用方和被调用方对于函数符合调用也必须要有一个名且的约定(例如被调函数 func(int a,int b)在参数a,b压栈的时候到底是从左往右一次压入栈还是从右往左,主调函数和被调函数之间大的约定必须都一致),只有双方都遵从着相同的约定,函数才能够被正确的调用,这样的约定被称之为"调用惯例",调用惯例一般包括以下几点:

(1)函数参数的传递顺序和方式:例如被调函数 func(int a,int b)在参数a,b压栈的时候到底是从左往右一次压入栈还是从右往左

(2)栈的维护方式:是主调函数还是被调函数销毁变量,可以由主调函数完成也可以由被调函数完成

在C语言里,存在着都哦个调用惯例,而默认的是 cdecl (cdecl 出栈由韩式调用方完成,参数传递:从右至左参数入栈),任何一i个没有显示指定调用管理的函数都是默认是 cdecl 惯例

c 复制代码
void func(int a,intb) 等价于 void _cdecl func(int a,int b)

_cdecl不是标准的关键字,在不同的编译器里可能右不同的写法,例如gcc里就不存在_cdecl这样的关键字,而是使用_attribute\_((cdecl))

main函数在栈区开辟的内存,所有子函数都可以使用

main函数在堆区开辟的内存,所有子函数都可以使用

子函数1在栈区开辟的内存,子函数1和子函数2都可以使用

子函数1在堆区开辟的内存,子函数1和子函数2都可以使用

子函数2在全局区开辟的内存,子函数1和main都可以使用

栈的生长方向和内存存放方向

c 复制代码
void test()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	
	printf("a = %d\n",&a);//a = -2091582748
	printf("b = %d\n",&b);//b = -2091582716
	printf("c = %d\n",&c);//c = -2091582684
	printf("d = %d\n",&d);//d = -2091582652
	/**
		可以看到a、b、c、d参数的地址都是不断变小
		因此栈的生长方向是向下的,从高地址到低地址
	**/
}
c 复制代码
void test()
{
	int a = 0xaabbccdd;
	
	unsigned char *p = &a;
	printf("%x\n",*p);//dd
	printf("%x\n",*(p+1));//cc
	/**
		小端模式:高位字节放在高地址,低位字节放在低地址
	**/
}

指针

指针不管什么类型,不管几级指针,占4字节

标准定义了NULL指针,它作为一个特殊的指针变量,表示不指向任何东西,要是一个指针为NULL,可以i给他赋值一个零值

野指针:指针指向一个已删除的对象或未申请受保护的内存
指针步长

指针变量加一的时候,要向后跳多少字节

指针的类型不单单决定指针的步长,还决定解引用的时候从给定地址开始取类型大小的字节数

c 复制代码
/**
	指针步长由指针类型来决定
**/
void test() 
{
	char *p = NULL;
	printf("%d\n",p);//0
	printf("%d\n",p+1);//1
	
	int *p1 = NULL;
	printf("%d\n",p1);//0
	printf("%d\n",p1+1);//4
}
c 复制代码
void test()
{
	char buf[1024] = {0};
	int a = 100;
	memcpy(buf+1,&a,sizeof(int));

	char *p = buf;
	printf("*(int *)(p+1) = %d\n", *(int *)(p+1));//*(int *)(p+1) = 100
}

指针的意义--间接赋值

c 复制代码
/**
	可以在其他函数里面修改本函数的值
**/
void change(int *p)//通过指针在此函数里面修改 a 的值
{
	*p= 100;
}

void test()
{
	int a = 10;
	change(&a);
	printf("a = %d\n",a);
}

指针做函数参数,具备输入和输出特性:

输入:主调函数分配内存

输出:被调函数分配内存

c 复制代码
/**
	指针的输入特性:主调函数分配内存,被调函数使用内存
**/
void printString(const char * src) 
{
	printf("%s\n",src);// I am Polly
}

void printArray(int **arr, int len)
{
	//arr[0]是char *类型的
	for(int i = 0;i < len;++i)
	{
		printf("%s\n",arr[i]);
	}
}

void test()
{
	//堆上分配内存
	char *s = malloc(sizeof(char) * 100);
	memset(s,0,100);
	strcpy(s,"I am Polly");
	printString(s);
	
	//数组名做函数参数就会退化为指向数组首元素的指针
	int arr[] = {1, 2, 3, 4, 5, 6};

	//栈上分配内存
	char * str[] = {
		"aaa",//存储在常量区
		"bbb",
		"ccc",
		"ddd",
		"eee"
	};
	int len = sizeof(str) / sizeof(str[0]);
	printArray(str,len);
}
c 复制代码
/**
	输出特性:被调函数分配内存,主调函数使用内存
**/
void allocateSpace(char **temp)
{
	char * p = malloc(100);
	memset(p,0,100);
	strcpy(p,"hello world");
	//指针的间接赋值
	*temp = p;
}

void test()
{
	char *p  = NULL;
	allocateSpace(&p);
	printf("p = %s\n",p);//p = hello world
	if(p != NULL)
	{
		free(p);
	}
}

字符串

字符串就相当于一个字符数组,但结尾要以0结束

c 复制代码
/**
	字符串拷贝
**/
void copyString(char *dst,const char *source)
{
	int len = strlen(source);
	for(int i = 0;i < len;++i)
	{
		dst[i] = source[i];
	}
	dst[len] = '\0';
}

/**
	指针方式拷贝
**/
void copyString1(char *dst,const char *source)
{
	while(*source != '\0')
	{
		*dst = *source;
		++dst;
		++source;
	}
	dst[len] = '\0';
}

void copyString1(char *dst,const char *source)
{
	while(*dst++ = *source++);
}

void test()
{
	char *source = "hello world";
	char buf[1024] = {0};//如果不初始化的话存储的数据就可能使乱码	
	copyString(buf,source);
	copyString1(buf,source);
}
c 复制代码
/**
	字符串翻转
**/
void reverseString(char *src)
{
	if(NULL == src)
	{
		return;
	}
	int len = strlrn(src);
	int start = 0;
	int end = len-1;
	while(start < end)
	{
		char temp = src[start];
		src[start] = src[end];
		src[end] = temp;
		
		++start;
		--end;
	}
}

/**
	指针实现字符串翻转
**/
void reverseString(char *src)
{
	int len = strlrn(src);
	char * pStart = src;
	char * pEnd = sdrc + len - 1;
	while(pStart < pEnd)
	{
		char temp = *pStart;
		*pStart = *pEnd;
		*pEnd = temp;

		++pStart;
		--pEnd;
	}
}

void test()
{
	char p[] = "hello world";
	reverseString(p);
}

sprintf

int sprintf(char *str,const char *format,...)

功能:根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,直到出现字符串结束符 '\0'为止

str:字符首地址

format:字符串格式,用法和printf()一样

返回值:

成功:实际格式化的字符个数

失败:-1

c 复制代码
void test()
{
	//格式化字符串
	char buf[1024] = { 0 };
	sprintf(buf,"hello %s!","Obama");
	printf("buf:%s\n",buf);//buf:hello Obama!

	//拼接字符串
	char *s1 = "hello";
	char *s2 = "world";
	memset(buf, 0, 1024);
	sprintf(buf,"%s %s",s1,s2);
	printf("buf:%s\n",buf);//buf:hello world

	//数字转换成字符串格式
	int number = 666;
	memset(buf, 0, 1024);
	sprintf(buf, "%d", number);
	printf("buf:%s\n",buf);//buf:666

	//格式化数字八进制 十六进制
	memset(buf, 0, 1024);
	sprintf(buf, "%o",number);
	printf("八进制:%s\n",buf);//八进制:1232
	sprintf(buf,"%x",number);
	printf("十六进制:%s\n",buf);//十六进制:29a
}
相关推荐
古希腊掌管学习的神26 分钟前
[LeetCode-Python版]相向双指针——611. 有效三角形的个数
开发语言·python·leetcode
赵钰老师27 分钟前
【R语言遥感技术】“R+遥感”的水环境综合评价方法
开发语言·数据分析·r语言
就爱学编程35 分钟前
重生之我在异世界学编程之C语言小项目:通讯录
c语言·开发语言·数据结构·算法
Oneforlove_twoforjob1 小时前
【Java基础面试题025】什么是Java的Integer缓存池?
java·开发语言·缓存
emoji1111111 小时前
前端对页面数据进行缓存
开发语言·前端·javascript
每天都要学信号1 小时前
Python(第一天)
开发语言·python
TENET信条1 小时前
day53 第十一章:图论part04
开发语言·c#·图论
北国无红豆1 小时前
【CAN总线】STM32的CAN外设
c语言·stm32·嵌入式硬件
生信圆桌1 小时前
【生信圆桌x教程系列】如何安装 seurat V5版本R包,最详细安装手册
开发语言·r语言
IT猿手1 小时前
最新高性能多目标优化算法:多目标麋鹿优化算法(MOEHO)求解TP1-TP10及工程应用---盘式制动器设计,提供完整MATLAB代码
开发语言·深度学习·算法·机器学习·matlab·多目标算法