C语言核心知识点Day02

1.函数调用模型

1.1 函数调用流程
	在经典的计算机科学中,栈被定义为一个特殊的容器,用户可以将数据压入栈中(入栈,push),
也可以将压入栈中的数据弹出(出栈,pop),但是栈容器必须遵循一条规则:先入栈的数据最后
出栈(先进后出).
	在经典的操作系统中,栈总是向下增长的。压栈的操作使得栈顶的地址减小,弹出操作使得栈顶地址增大。
栈在程序运行中具有极其重要的地位。最重要的,栈保存一个函数调用所需要维护的信息,这通常被称为堆栈帧(Stack Frame)或者活动记录(Activate Record).一个函数调用过程所需要的信息一般包括以下几个方面:
	函数的返回地址;
	函数的参数;
	临时变量;
	保存的上下文:包括在函数调用前后需要保持不变的寄存器。
	**宏函数不是函数,宏函数在一定场景下效率更高,对于频繁使用的,并且代码量短小的
函数 因为宏函数没有与普通函数调用的开销(函数压栈,跳转,返回等),因此使用宏函数。**

1.2 调用惯例
一个调用惯例一般包含以下几个方面:
	函数参数的传递顺序和方式
	
	1.函数的传递有很多种方式,最常见的是通过栈传递。函数的调用方先将将参数压入栈中,
函数自己再从栈中将参数取出。对于有多个参数的函数,调用惯例要规定函数调用方将参数压
栈的顺序:从左向右,还是从右向左。有些调用惯例还允许使用寄存器传递参数,以提高性能。在C语言中,对于子函数的调用中,一般情况下都是(假设都是在栈区存储的变量)
	(1):先将子函数的形参压入栈中;
	(2):然后将传入的实参的值放入到形参中
	(3):然后跳转到返回地址,也就是在主函数中调用子函数的代码的下一行代码的地址;
	(4):然后再执行子函数,将子函数中内部定义的局部变量压入栈中,局部变量的值如果
	 和子函数的形参有关,就把形参的值赋与局部变量,如果没有使用形参的值,就直接在内
	 存中存储定义局部变量时赋予的值。
	(5):若需要返回值,且返回值较小的时候,则将运算的运算结果存储与寄存器中,将寄
	存器的值赋予主函数中定义的用来存储返回值的变量,否则将内容存储在栈中,将栈中的内
	容赋予主函数中接收的变量;
	(6):函数调用过程结束,释放栈区空间

	2.栈的维护方式
		为了在链接的时候对调用惯例进行区分,调用惯例要对函数本身的名字进行修饰。不同
	的调用惯例有不同的名字修饰策略。
		在c语言里,存在着多个调用惯例,而默认的是cdecl(由函数调用方释放内存,参数
	由右向左进行入栈).任何一个没有显示指定调用惯例的函数都是默认是cdecl惯例。比如
	我们上面对于func函数的声明,它的完整写法应该是:
		 int _cdecl func(int a,int b);
		注意: _cdecl不是标准的关键字,在不同的编译器里可能有不同的写法,例如gcc里就
	不存在_cdecl这样的关键字,而是使用__attribute__((cdecl)).

1.3 栈的生长方向和内存存放方向
	1.栈的生长方向:自顶向下生长,由高地址(栈顶)向低地址(栈底)生长
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
void test01()
{
	int a = 10;
	int b = 20;
	int c = 30;
	int d = 40;
	printf("%p\n", &a);
	printf("%p\n", &b);
	printf("%p\n", &c);
	printf("%p\n", &d);
}

int main()
{
	test01();
	return 0;
}
	2.单个数据在内存中的存储,比如0xaabbccdd,在内存中的存储顺序应该为ddccbbaa,
	即高位字节存放的是数据的高位地址(小端模式)
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>

void test()
{
	int a = 0xaabbccdd;
	unsigned char* p = &a;
	printf("%x\n", *p);
}

int main()
{
	test();
	return 0;
}

2.指针

2.1 空指针和野指针
(1):空指针
	NULL指针,它作为一个特殊的指针变量,表示不指向任何东西。要使一个指针为NULL,可
以给它赋值一个零值。为了测试一个指针百年来那个是否为NULL,你可以将它与零值进行比较。
	对指针解引用操作可以获得它所指向的值。但从定义上看,NULL指针并未执行任何东西,
因为对一个NULL指针因引用是一个非法的操作,在解引用之前,必须确保它不是一个NUL针。
 (2):野指针
 	在使用指针时,要避免野指针的出现:
	导致野指针的三种抢矿:
	1.指针变量未初始化(牢记指针必须初始化)
		指针变量在创建的同时应当被初始化,要么将指针设置为NULL,要么让它指向合法的内
	存。
	2.指针释放后未置空(使用指针后记得置NULL)
		有时指针在free或delete后未赋值 NULL,便会使人以为是合法的。别看free和delete的名
	字(尤其是delete),它们只是把指针所指的内存给释放掉,但并没有把指针本身干掉。此
	时指针指向的就是“垃圾”内存。释放后的指针应立即将指针置为NULL,防止产生“野指针”。
	
	3.指针操作超越变量作用域(不要返回局部变量的地址)
		不要返回指向栈内存的指针或引用,因为栈内存在函数结束时会被释放。

2.2 指针的步长
	指针的步长指的是,当指针+1时候,移动1个指针类型字节大小的距离。
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#include<stddef.h>

//指针步长:指针变量+1 要想后跳(指针类型大小)的字节

//指针的类型:不单单决定指针的步长,还决定解引用的时候从给定地址开始取类型大小的字节数
void test01()
{
	//步长
	char* p = NULL;
	printf("%d\n", p);
	printf("%d\n", p + 1);

	printf("-----------------\n");
	int* p2 = NULL;
	printf("%d\n", p2);
	printf("%d\n", p2 + 1);

	printf("-----------------\n");

	char buffer[1024] = { 0 };
	int a = 100;
	memcpy(buffer + 1, &a, sizeof(int));

	char* p3 = buffer;
	//从p3+1的位置开始找,找连续的四个字节(int),然后取出结果
	printf("*p3[1]---*p3[4] = %d\n", *(int*)(p3 + 1));
}


struct Person
{
	int a;
	char b;
	char buf[64];
	int d;
};

void test02()
{
	struct Person p1 = { 10,'a',"hello python!",20 };
	char b;
	//offsetof是计算偏移量的宏函数,定义在stddef.h头文件中,即计算b距离Person类型的首地址的偏移量
	printf("结构体中b的偏移量为:%d\n", offsetof(struct Person,b));

	printf("d = %d\n",*(int* )((char* )&p1 + offsetof(struct Person, d)));
}

int main()
{
	test01();
	test02();
	return 0;
}
2.3 指针作函数参数
	指针做函数参数,具备输入和输出特性:
		输入:主调函数分配内存,被调函数使用内存
		输出:被调函数分配内存,主调函数释放内存
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<stdlib.h>
#include<string.h>


void printString(const char* arr)
{
	printf("%s\n",arr);
}

void printfArrString(const char** arr,int len)
{
	for (int i = 0; i < len; i++)
	{
		printf("%s\n",arr[i]);
	}
}
//1.主调函数分配内存,被调函数使用内存 指针的输入特性
void test01()
{
	//堆上分配内存
	char* s = malloc(sizeof(char) * 100);
	memset(s, 0, 100);
	strcpy(s, "hello world");
	printString(s);
	free(s);

	//栈上分配内存
	char* str[] = { "aaaaaa","bbbbbb","cccccc","dddddd","eeeeee", };
	int strLen = sizeof(str) / sizeof(str[0]);
	//printf("%d\n", strLen);
	printfArrString(str, strLen);
}

//2.输出特性 被调函数分配内存,主调函数使用内存
void allocateSpace(char** temp)
{
	char* p = malloc(100);
	memset(p, 0, 100);
	strcpy(p, "hello world");

	*temp = p;
}

void test02()
{
	char* p = NULL;
	allocateSpace(&p);
	printf("%s\n", p);

	if (p != NULL)
	{
		free(p);
		p = NULL;
	}
}

int main()
{
	//test01();
	test02();
	return 0;
}
2.4	指针函数和函数指针的区别(转)
	2.4.1 指针函数,就不多做详解了,即函数的返回值为一个指针。
		指针函数的写法:
		int *fun(int x,int y);
		int * fun(int x,int y);
		int* fun(int x,int y);
	注:在调用指针函数时,需要一个同类型的指针来接收其函数的返回值。
不过也可以将其返回值定义为 void*类型,在调用的时候强制转换返回值为
自己想要的类型来接受。

	2.4.2.函数指针
	2.4.2.1 函数指针定义
		函数指针,其本质是一个指针变量,该指针指向这个函数。总结来说,
	函数指针就是指向函数的指针。
		格式:类型说明符 (*函数名) (参数)
		示例:
#include<iostream>
using namespace std;

int add(int x, int y) {
    return x + y;
}
int sub(int x, int y) {
    return x - y;
}
//函数指针
int (*fun)(int x, int y);

int main(int argc, char* argv[])
{
    //QApplication a(argc, argv);
    //第一种写法
    fun = add;
    cout << "(*fun)(1,2) = " << (*fun)(1, 2) << endl;;
    //第二种写法
    fun = &sub;
    cout << "(*fun)(5,3) = " << (*fun)(5, 3) <<"\t"<< fun(5, 3)<<endl;

    system("pause");
    return 0;
}
2.4.3 函数指针和指针函数的区别:
	1.指针函数本质是一个函数,其返回值为指针;
	  函数指针本质是一个指针,其指向一个函数。
	2.指针函数:int* fun(int x,int y);
	  函数指针:int (*fun)(int x,int y);
	  可以理解为,指针函数的*是属于数据类型的,而函数指针的星号是
	属于函数名的。
	  再简单一点,可以这样辨别两者:函数名带括号的就是函数指针,
	否则就是指针函数。
	
	总结:指针函数就是一个返回值为指针的函数,函数指针是一个指向
	函数的指针。一个很简单的判定方法就是观察(*),比如 int (*p)(int,int);
	有括号,*与p结合,*p就是个指针,
		**      指向返回值为整型且有两个整型参数的函数     **。
	int*p(int,int); 没有括号,*与int结合,int*为返回类型,p就是一
	个函数名,这时就是一个指针函数,同样有两个int形参,只不过返回值
	类型为int*。

(转自详解指针函数和函数指针

3.字符串

3.1字符串格式化
	1.sprintf函数
	#include <stdio.h>
	int sprintf(char *str, const char *format, ...);
	功能:
	     根据参数format字符串来转换并格式化数据,然后将结果输出到str指定的空间中,
	直到出现字符串结束符 '\0'  为止。
	参数: 
		str:字符串首地址
		format:字符串格式,用法和printf()一样
	返回值:
		成功:实际格式化的字符个数
		失败: - 1
		
	2.sscanf函数
	#include <stdio.h>
	int sscanf(const char *str, const char *format, ...);
	功能:
	    从str指定的字符串读取数据,并根据参数format字符串来转换并格式化数据。
	参数:
		str:指定的字符串首地址
		format:字符串格式,用法和scanf()一样
	返回值:
		成功:实际读取的字符个数
		失败: - 1
#define _CRT_SECURE_NO_WARNINGS
#include<stdio.h>
#include<string.h>
#include<stdlib.h>

void test01()
{
	char ch1[] = "helloworld@qq.conm";
	char buffer1[1024] = { 0 };

	sscanf(ch1, "%[^@]", buffer1); //截取到helloworld
	//sscanf(ch1, "%s@%s", buffer1,buffer2);
	printf("%s\n", buffer1);
	sscanf(ch1, "%*[^@]@%s", buffer1);//任意一个非@符号的字符都不截取,然后匹配到@后截取所有字符串
	printf("%s\n", buffer1);

}

int main()
{
	test01();
	return 0;
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值