数组和指针
前言
1.C中只支持一维数组,和静态数组(数组的大小在编译的时候就必须作为一个常数确定下来)
2.声明了一个数组后,数组的大小也就确定了,程序员可以通过数组名获得这 个数组下标为0的元素的指针
多维数组的解释
int calendar[12][31];
(1)calendar代表了拥有12个数组类型的元素(关键是数组类型)
(2)每个数组类型的元素,都拥有31个整数类型元素
数组名和指针的关系
1.数组名说明了其指代实体是个数据结构
2.数组名是指针常量,指向指代实体
3.指向数组的指针,仍然是指针类型的变量而已,大小一般4字节,内容是数组的地址
PS:指针常量和常量指针
指针常量:指针自身的值是一个常量,不可改变,始终指向同一个地址。在定义的同时必须初始化
int * const p = &a;
常量指针:
指针指向的内容是不可改变的,指针看起来好像指向了一个常量(为什么是看起来,是因为a是变量,只是这个指针不能修改它的值,但是可以指向别的地址)
int a = 10, b = 20;
const int *p = &a;
p = &b; // 指针可以指向其他地址,但是内容不可以改变
因此如果概况起来就是,数组名是个指针常量,只能指向首地址且不可更改的
而指向数组的指针是个变量,刚指向的时候是首地址,而它可以通过++移动到下一个地址上去
数组作为函数入口参数
C语言的传参规则是传值不传址,也就是所有的参数是通过将参数的值复制到堆栈中(或者传参寄存器)来进行传递的。 按照这个规则的话,如果允许传递数组作为参数,编译器应该将数组的所有值通过压入堆栈来 进行传递,显然这样的效率是非常低下的。
C语言在这个问题上作了一个折中:当参数 为数组时,真正传递的是数组的首地址的值,而不是数组本身。而在函数内部,用来表示数组 的形参其实已经退化为一个局部指针变量,
下图中,形参的a数组,实际上编译器就会处理成指针变量了,因此甚至可以对a++
(可以理解成,a 指向了原有的数组首地址)
字符串数组和指向字符串数组的指针
char * p=“hello world!”
指向的是一串常量字符串而已,存储的是h所在的首地址,常量字符串在常量区(ROM这种)是是无法更改数据的
char a[]="hello, world!”
这个是数组变量,可以修改数据
注:指针也可以起到数组名的作用,比如p指向a后,可以用p[0]的方式操作数组内容
函数指针
<函数返回值类型〉(*函教指针变量名)(函数的参数列表);
举例子:
int * myfunction( int)
int (*fp)(int)
fp=myfunction;
定义了一个名字叫fp的函数指针类型的变量
然后指向了myfunction函数,其内容为myfunction的函数入口地址
易错辨析
(1)int * fp( int);//这个是个函数,返回值为整型的指针 int*就是这个意思
(2)
这个是函数指针数组,参数是int形式,返回值是整型指针
这个首先
(1) int (fp)(int)---------》void (fp)(void)
(2)强制类型转换
比如int a; char b;b=(char)a;这一步就是强制a转换成char型,就是去掉定义的变量名,然后加括号就是强制类型转换了
比如 int (fp)(int)是定义了一个名字fp的函数指针,那么强制类型转换成函数指针就是
(int ()(int)),然后对于void就是
(void ()(void))0,意味着把0变成一个函数指针,只是无返回值且无参数的那种
(3)最后一步是调用这个指针对象,如下图的例子,需要在前面加 (**(void ()(void))0)
函数指针的作用
(1)多态函数
为解决一个接口,多种方法的问题
比如,两数运算,接口相同,可能有加减乘除
(2)回调函数
回调的意思是,一般来说,都是用户调用操作系统的api实现某些功能,回调是操作系统调用用户写的代码(底层调用上层)
举例说明:操作系统创建了定时器任务,每到时间就进行激活,首先会检查是否有用户创建的任务,有则调用此任务,如果没有就默认代码执行
用户就是通过函数指针传递给定时器此任务的
(3)多线程
解释线程和进程
https://blog.csdn.net/beidaol/article/details/89135277
多线程函数实例
API实现方法
#include <stdio.h>
#include <Windows.h>
DWORD WINAPI myfun1(LPVOID lpParameter);//线程函数声明,因为其他的都是默认缺省,这里不同的只有线程地址(函数名)
DWORD WINAPI myfun2(LPVOID lpParameter);
DWORD WINAPI myfun3(LPVOID lpParameter);
int main()
{
HANDLE h1,h2,h3;
h1=::CreateThread(NULL,0,myfun1,NULL,0,NULL);//创立线程后,线程函数立即执行myfun1地址
h2=::CreateThread(NULL,0,myfun2,NULL,0,NULL);
h3=::CreateThread(NULL,0,myfun3,NULL,0,NULL);
::CloseHandle(h1);//执行完就关闭句柄
::CloseHandle(h2);
::CloseHandle(h3);
::Sleep(10000);//让小黑屏多留一会,这三个线程函数的输出并不一定是123的顺序执行
return 0;
}
DWORD WINAPI myfun1(LPVOID lpParameter) //实现1线程函数
{
printf("“I am tread 1\r\n");
return 0;
}
DWORD WINAPI myfun2(LPVOID lpParameter) //实现2线程函数
{
printf("“I am tread 2\r\n");
return 0;
}
DWORD WINAPI myfun3(LPVOID lpParameter) //实现3线程函数
{
printf("“I am tread 3\r\n");
return 0;
}
/*
说明:调用的是api函数,
HANDLE CreateThread(
LPSECURITY_ATTRIBUTES lpThreadAttributes,
DWORD dwStackSize,
LPTHREAD_START_ROUTINE lpStartAddress,
LPVOID lpParameter,
DWORD dwCreationFlags,
LPDWORD lpThreadId );
lpThreadAttributes是一个指向结构体SECURITY_ATTRIBUTES的指针,表示指定新建线程的安全属性。该参数可以设置为NULL,表示创建线程时使用默认的安全属性。
dwStackSize指定线程初始化时地址空间的大小。如果这个参数指定为0,那么新创建的线程的地址空间大小与调用该函数的线程地址空间大小一样。
lpStackAddress将指定该线程的线程函数的地址。当线程创建成功后,新建线程将调用该线程函数执行某个功能。
lpParameter表示将要传递给新建线程的命令行参数,新建线程可以根据该命令参数的不同而执行不同的功能。
dwCreationFlags用于指定新建线程创建后是否立即执行。有两个状态值,一个是CREATE_SUSPENDED,作用是创建线程成功后暂停运行;另一个是0,作用是创建线程成功后立即运行。
lpThreadId表示新建线程的ID号,一般设置为NULL。
h3=::CreateThread(NULL,0,myfun3,NULL,0,NULL);
//为1.默认安全属性2.线程初始化地址空间大小和调用此函数的一样 3.线程函数地址(函数名是入口地址)4.命令行参数传递5.创立后立即执行6.ID为NULL
*/
C库函数实现
http://c.biancheng.net/view/425.html