文章目录
一、typedef & define
问题
typedef void (*system_irq_handler_t) (unsigned int giccIar, void *param);
ans:函数指针,看④
1、typedef 关键字
https://blog.youkuaiyun.com/chenwang1824/article/details/131183658
http://t.csdnimg.cn/C9iSY
C语言允许用户使用 typedef 关键字来为类型取一个新的名字,typedef 的真正含义是给一个已经存在的类型名称起一个别名。
注意是类型,而非变量
①typedef 定义基本类型
typedef signed char int8_t;
typedef short int16_t;
typedef int int32_t;
typedef long long int64_t;
typedef unsigned char uint8_t;
typedef unsigned short uint16_t;
typedef unsigned int uint32_t;
typedef unsigned long long uint64_t;
上面是为已有的数据类型起了一个别名,使用 int8_t 的时候 就和使用 signed char 类型是一致的。
②typedef 定义指针和数组类型
// pointer 一个指向 int 类型的指针类型
typedef int *pointer;
typedef int *pointer 定义了一个指针类型,当我们使用 pointer p 声明一个指针变量就和使用 int *p 的含义是一样的
// 一个数组类型 pointerarray
typedef int *pointerarray[10];
void main()
{
int a[10] = {0};
pointerarray p = {NULL};
for (int i = 0; i < 10; i++) {
p[i] = &a[i];
}
for (int i = 0; i < 10; i++) {
printf("p[%d] = %p \n", i, p[i]);
}
printf("\n");
return 0;
}
/*output:
p[0] = 0053F968
p[1] = 0053F96C
p[2] = 0053F970
p[3] = 0053F974
p[4] = 0053F978
p[5] = 0053F97C
p[6] = 0053F980
p[7] = 0053F984
p[8] = 0053F988
p[9] = 0053F98C*/
ypedef int *pointerarray[10] 定义了一个数组类型,当我们使用 pointerarray p 声明变量 p,表示声明一个数组 p ,数组大小为10,数组的元素类型为 int *
// 数组指针类型arraypointer, 数组元素为 int 类型,数组大小为10
typedef int (*arraypointer)[10];
ypedef int (*arraypointer)[10]; 定义了一个数组指针类型,当我们使用 arraypointer p 声明变量 p,表示声明一个指针变量 p,p 指向一个元素类型为 int,大小为 10 的数组. 数组指针和普通指针不同。
https://blog.youkuaiyun.com/Qiuhan_909/article/details/125795107
// 数组类型 fixedarray,数组元素为 int 类型,数组大小为10
typedef int fixedarray[10];
// 二维数组类型,数组元素为 int 类型,数组大小为10 *10
typedef int fixedsarray[10][10];
typedef int fixedarray[10];
typedef int fixedsarray[10][10];
定义了两个数组类型,fixedarray a,表示定义了一个元素类型为 int,大小为10 的数组,同理fixedsarray 表示的是 10x10 的二维数组
③typedef 定义结构体类型
typedef struct {
int x;
int y;
int width;
int height;
} stBufferinfo;
typedef struct {
int x;
int y;
int width;
int height;
} *pstBufferinfo;
stBufferinfo 定义了一个结构类型,stBufferinfo bufferinfo 就表示定义了一个结构体,结构体类型为stBufferinfo,结构体变量名为 bufferinfo。
pstBufferinfo 定义了一个指针类型,指向的是pstBufferinfo 定义的结构体类型。
typedef enum {
MON,
TUE,
WED,
THR,
FRI,
SAT,
SUN,
} enweekday;
typedef enum {
sMON,
sTUE,
sWED,
sTHR,
sFRI,
sSAT,
sSUN,
} *penweekday;
同理 enweekday 和 penweekday 也是定义了了一个枚举类型和指向枚举的指针类型
④typedef 定义函数
函数指针
#include<stdio.h>
int fun(void)
{
return 20;
}
typedef int( *f)(void) ;
int main()
{
f a = fun;
printf("%d", (a()));
return 0;
}
函数的重定义
#include<stdio.h>
int fun(void)
{
return 20;
}
typedef int(f)(void) ;
int main()
{
f* a = fun;
printf("%d", (a()));
return 0;
}
⑤typedef 结构体指针地址
http://t.csdnimg.cn/pgod7
结构体指针的地址,与它指向的结构体的第一个成员地址是一样的。
定义结构体指针与定义普通指针类似,只不过指向的是结构体类型。
typedef struct
{
.....
}test_type;
#define TEST_BASE (0X02290000)
#define test_struct ((test_type*)TEST_BASE
可以看出test_struct 是个宏定义指针,是一个指向地址 TEST_BASE 的结构体指针,结构体为test_type。
2、define 关键字
#define 是 C 指令,用于为各种数据类型定义别名
typedef 仅限于为类型定义符号名称,#define 不仅可以为类型定义别名,也能为数值定义别名,比如您可以定义 1 为 ONE。
typedef 是由编译器执行解释的,#define 语句是由预编译器进行处理的。
二、#if #else #endif
#if(条件编译)
#if的使用和if else的使用非常相似,一般使用格式如下
#if 整型常量表达式1
程序段1
#elif 整型常量表达式2
程序段2
#else
程序段3
#endif
三、struct
声明结构体
/* LCD控制参数结构体 */
struct tftlcd_typedef{
unsigned short height; /* LCD屏幕高度 */
unsigned short width; /* LCD屏幕宽度 */
unsigned char pixsize; /* LCD每个像素所占字节大小 */
unsigned short vspw;
unsigned short vbpd;
unsigned short vfpd;
unsigned short hspw;
unsigned short hbpd;
unsigned short hfpd;
unsigned int framebuffer; /* LCD显存首地址 */
unsigned int forecolor; /* 前景色 */
unsigned int backcolor; /* 背景色 */
unsigned int id; /* 屏幕ID */
};
定义结构体变量
struct tftlcd_typedef tftlcd_dev;
不是起别名↓
四、inline关键字
http://t.csdnimg.cn/Tu2iR
https://blog.youkuaiyun.com/qq_30095023/article/details/132426517
C 语言中函数调用与返回时会有部分的额外开销,如果在函数需要调用的次数非常多时,这些额外开销就会产生积累效应。
在调用子函数时,通常要经过 保存当前的指令位置,程序流跳转到子函数,执行子函数,返回之前的指令位置 这个过程,但对于那些经常被调用的小函数来说,这样的调用过程会影响程序效率。
inline 函数的调用过程与此不同,它会告诉编译器把那些小函数编译后的机器码放到调用函数的地方,这样就节省了函数调用的时间。
总结
1、内联函数相比宏函数,会进行语法检查。宏函数是在预处理阶段生效,内联函数是在编译阶段进行语法检查然后替换。
·
2、内联函数相比普通函数,少了上下文切换的步骤所以执行会更快一些。
·
3、内联函数被多次调用,会使固件大小膨胀,内联函数的高速是以空间来换时间。
·
4、内联函数不可递归。
·
5、如果函数内容太过于复杂,编译器会忽略inline关键字,把他当成普通函数来处理。
五、extern关键字
http://t.csdnimg.cn/04t89
extern是C语言中的一个关键字,一般用在变量名前或函数名前,作用是用来说明“此变量/函数是在别处定义的,要在此处引用”。
a.c 中 定义了一个结构体struct1 str1,中a.h中使用extern 关键字声明 extern struct1 str1,在main.c 中include a.h 这样就可以调用 str1了。
六、printf函数
#include<stdio.h>
int main()
{
double a = 1.0;
printf("%f\n", a / 3 ); // %f 用法
printf("%s","haha" ); // %s 用法
int a=67;
printf("%x\n",a ); //%x 、%X 、%#X 用法
printf("%X\n", a);
printf("%#X\n", a);
return 0;
}
/*代码输出结果为:0.333333 */
/*代码输出结果为:haha */
/*代码输出结果为:37
37
O#37
*/
七、特殊字符序列>\r\n
http://t.csdnimg.cn/3yeEr
\n’表示将光标当前位置移到下一行的开头;
'\r’表示将光标当前位置移到本行的开头。(仅仅将光标移至本行开头,从开头开始输出)
\r\n是使得光标移动到了下一行的开头
另外,C 语言中也提供了特殊字符序列来表示其他一些控制字符,如制表符、响铃符等。以下是一些常见的特殊字符序列及其含义:
\b:退格符(Backspace)
\f:换页符(Form Feed)
\t:制表符(Tab)
\v:垂直制表符(Vertical Tab)
\a:响铃符(Alert)
八、memset()函数
memset 是 C 语言中的一个用来设置内存的函数,它通常用于初始化内存空间。其原型为:
void * memset ( void * ptr, int value, size_t num );
其中,ptr 是需要填充的内存的指针,value 是需要设置的值,num 是需要设置的字节数。
char str[50];
memset(str, 0, sizeof(str));
这段代码将字符数组 str 中所有的元素初始化为 0。
九、sprintf()函数
原文链接:https://blog.youkuaiyun.com/u012730525/article/details/136390157
在 C 语言中,有许多用于处理字符串的函数,其中一个非常强大和灵活的函数就是 sprintf()。它的功能是将各种类型的数据格式化为字符串,并存储到一个字符数组中。它的原型如下:
int sprintf(char *str, const char *format, ...);
其中,str 是指向一个字符数组的指针,用于存储生成的字符串;format 是一个格式化字符串,用于指定要输出的内容和格式;后面的省略号表示可变参数列表,用于提供与格式化字符串中的占位符相对应的值。
格式化字符串
格式化字符串是 sprintf() 函数的核心,它决定了输出字符串的内容和格式。格式化字符串中可以包含普通的字符,也可以包含以 % 开头的格式说明符(format specifier),用于占据一个位置,并在运行时用相应的参数值替换。格式说明符的一般形式是:
%[flags][width][.precision][length]specifier
其中,各个部分的含义如下:
specifier(说明符):表示要输出的数据类型,如 c 表示字符,d 表示十进制整数,f 表示浮点数,s 表示字符串等。
flags(标志):表示输出的格式修饰,如 - 表示左对齐,+ 表示显示正负号,0 表示用 0 填充空位等。
width(宽度):表示输出的最小字符数,如果实际输出的字符数小于宽度,则会用空格或 0 填充;如果大于宽度,则不会截断。
.precision(精度):表示输出的精确度,对于整数类型,表示输出的最小位数,不足则用 0 填充;对于浮点类型,表示输出的小数位数;对于字符串类型,表示输出的最大字符数。
length(长度):表示输出的数据长度,如 h 表示短整型,l 表示长整型,L 表示长双精度型等。
下面举一些例子说明格式说明符的用法:
char str[80];
sprintf(str, "%c", 'A'); // 输出字符 A
sprintf(str, "%d", 123); // 输出十进制整数 123
sprintf(str, "%x", 255); // 输出十六进制整数 ff
sprintf(str, "%f", 3.14); // 输出浮点数 3.140000
sprintf(str, "%s", "Hello"); // 输出字符串 Hello
sprintf(str, "%10d", 123); // 输出宽度为 10 的十进制整数,右对齐,空位用空格填充: 123
sprintf(str, "%-10d", 123); // 输出宽度为 10 的十进制整数,左对齐,空位用空格填充:123
sprintf(str, "%010d", 123); // 输出宽度为 10 的十进制整数,右对齐,空位用 0 填充:0000000123
sprintf(str, "%+d", 123); // 输出带正负号的十进制整数:+123
sprintf(str, "%.3d", 123); // 输出至少 3 位的十进制整数,不足则用 0 填充:123
sprintf(str, "%.3d", 12); // 输出至少 3 位的十进制整数,不足则用 0 填充:012
sprintf(str, "%.3f", 3.14); // 输出保留 3 位小数的浮点数:3.140
sprintf(str, "%.3s", "Hello"); // 输出最多 3 个字符的字符串:Hel
sprintf(str, "%ld", 123456789L); // 输出长整型数:123456789
sprintf(str, "%Lf", 3.1415926535897932384626433832795L); // 输出长双精度型数:3.141593
十、指针
1.什么是指针
指针理解的2个要点:
- 指针是内存中一个最小单元的编号,也就是地址
- 平时口语中说的指针,通常指的是指针变量,是用来存放内存地址的变量
总结:指针就是地址,口语中说的指针通常指的是指针变量。
一个字节给一个对应的地址是比较合适的,即每个字节都有一个指向它的指针变量。
在32位的机器上,地址是32个0或者1组成二进制序列,那地址就得用4个字节的空间来存储,所以
一个指针变量的大小就应该是4个字节。
那如果在64位机器上,如果有64个地址线,那一个指针变量的大小是8个字节,才能存放一个地
址。
2.指针类型的作用
2.1 指针的类型决定了指针移动一次的步长
#include<stdio.h>
int main() {
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
double* pd = &n;
printf("%p\n", &n); //000000EE700FFB14
printf("%p\n", pc); //000000EE700FFB14
printf("%p\n", pc + 1); //000000EE700FFB15
printf("%p\n", pc + 2); //000000EE700FFB16
printf("%p\n", pi); //000000EE700FFB14
printf("%p\n", pi + 1); //000000EE700FFB18
printf("%p\n", pi + 2); //000000EE700FFB1C
printf("%p\n", pd + 1); //000000EE700FFB1C
return 0;
}
pc + 1:char型指针+1,代表每次加了一个字节,因为一个字节就代表一个char
pi + 1:int型指针+1,代表每次增加4个字节,因为一个int 占4字节
pd + 1:double型指针+1,代表每次增加8个字节,因为一个double占8字节
2.2 指针的类型决定了解引用时的权限
#include<stdio.h>
int main() {
int n = 0x11223344; // &n:0x00000049692FF514 存放内容: 44 33 22 11
char* pc = (char*)&n;
int* pi = &n;
*pc = 0; //&n:0x00000049692FF514 存放内容: 00 33 22 11
*pi = 0; //&n:0x00000049692FF514 存放内容: 00 00 00 00
return 0;
}
pc = 0 :char型指针, char 的指针解引用就只能访问一个字节 ,44 33 22 11->00 33 22 11
pi = 0:int 的指针的解引用就能访问四个字节, 44 33 22 11->00 00 00 00
3.规避野指针
- 指针初始化
- 小心指针越界
- 指针指向空间释放及时置NULL
- 避免返回局部变量的地址
- 指针使用之前检查有效性,是否位NULL
4.指针运算
4.1 指针±整数
#define N_VALUES 5
float values[N_VALUES]; //首地址:0x00007FF7F1C0D760
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];) // values[5]的地址: 0x00007FF7F1C0D774
{
*vp++ = 0; // 等效于 *vp = 0; vp++;
}
values[5]也是有地址的,这个不是访问越界。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
4.2 指针- 指针
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 };
printf("%d %d %d", &arr[9], &arr[0], &arr[9] - &arr[0]); //473298364 473298328 9
指针相减的前提是它们都指向同一块地址。相减的结果是它们之间的元素个数。
虽然 473298364 - 473298328 = 4*9 = 36,但&arr[9] - &arr[0] = 9,即相隔9个元素
4.3 指针的关系运算
最后元素之后的地址:
#define N_VALUES 5
float values[N_VALUES]; //首地址:0x00007FF7F1C0D760
float *vp;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];) // values[5]的地址: 0x00007FF7F1C0D774
{
*vp++ = 0; // 等效于 *vp = 0; vp++;
}
values[5]也是有地址的,这个不是访问越界。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的那个内存位置的指针比较,但是不允许与 指向第一个元素之前的那个内存位置的指针进行比较。
首地址之前的地址:
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
5.指针与数组
数组名表示的是数组首元素的地址
但有两个例外:
1.sizeof(数组名):数组名表示整个数组,计算的是整个数组的大小。
2.&数组名:数组名表示整个数组,取出的是整个数组的地址。
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
}
//output
//&arr[0] = 0000006DE930F9A8 <=== = > p + 0 = 0000006DE930F9A8
//& arr[1] = 0000006DE930F9AC <=== = > p + 1 = 0000006DE930F9AC
//& arr[2] = 0000006DE930F9B0 <=== = > p + 2 = 0000006DE930F9B0
//& arr[3] = 0000006DE930F9B4 <=== = > p + 3 = 0000006DE930F9B4
//& arr[4] = 0000006DE930F9B8 <=== = > p + 4 = 0000006DE930F9B8
//& arr[5] = 0000006DE930F9BC <=== = > p + 5 = 0000006DE930F9BC
//& arr[6] = 0000006DE930F9C0 <=== = > p + 6 = 0000006DE930F9C0
//& arr[7] = 0000006DE930F9C4 <=== = > p + 7 = 0000006DE930F9C4
//& arr[8] = 0000006DE930F9C8 <=== = > p + 8 = 0000006DE930F9C8
//& arr[9] = 0000006DE930F9CC <=== = > p + 9 = 0000006DE930F9CC
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
//1 2 3 4 5 6 7 8 9 0
// 数组的 [] 是一个操作符, arr[i] 中, arr与 i 都是操作数,且满足交换率
// arr[2] --> *(arr + 2) --> *(2 + arr) --> 2[arr]
// 同时,arr[2] = *(arr+2) = *(p+2) = p[2] = *(2+p) = 2[p]
printf("\r\n%d %d %d %d", arr[2], 2[arr], p[2], 2[p]);
//3 3 3 3
return 0;
}
其中,&arr[i] 代表的地址和 p+i 代表的地址是一样的,即 *(p + i) = arr[ i ].
数组的 [] 是一个操作符, arr[i] 中, arr与 i 都是操作数,且满足交换率
有,arr[2] --> *(arr + 2) --> *(2 + arr) --> 2[arr]
同时,arr[2] = *(arr+2) = *(p+2) = p[2] = *(2+p) = 2[p]
6.二级指针
指针变量也是变量,是变量就有地址,存放指针变量的地址的就是 二级指针 。
同理,存放二级指针变量的地址的就是 三级指针,依此类推。
int a = 10;
int* pa = &a;
int** ppa = &pa;
printf("&a:%p\npa: %p\n&pa: %p\nppa: %p\n", &a, pa, &pa, ppa);
printf("\n*ppa: %p\npa: %p\n", *ppa, pa);
printf("\n*pa: %d\na: %d", *pa, a);
printf("\n**ppa: %d", **ppa);
//output
//&a:0000001D4914FCA4
//pa : 0000001D4914FCA4
//& pa : 0000001D4914FCC8
//ppa : 0000001D4914FCC8
//
//* ppa : 0000001D4914FCA4
//pa : 0000001D4914FCA4
//
//* pa : 10
//a : 10
//* *ppa : 10
int* pa = &a;
‘ * ’ 代表pa是指针变量,int 代表pa 指向的 a 是 int型的变量,‘ * ’靠近int 还是 a没有区别。
int** ppa = &pa; //int** ppa = int* *ppa = int **ppa = &pa;
’ * '代表ppa是指针变量,int * 代表ppa指向的 pa 是 int型的指针,两个‘ * ’靠近那边都没区别。
pa = a 的地址,*pa = a
同理,ppa = pa 的地址, *ppa = pa = a 的地址,**ppa = *pa = a 本身。
7.指针数组
数组的声明:数据类型 数组名[ num ],找到 数组名[ num ] 后,其余就是数据类型。
指针数组是数组,是存放指针的数组。例如:
int arr1[5];
char arr2[6];
int* arr3[5];//是什么?
arr3是一个数组,有五个元素,每个元素是一个整形指针。
同理,有char* charr[5], float* farr[5]等等。
指针数组和二维数组的不同
int a[] = { 1,2,3,4,5 };
int b[] = { 2,3,4,5,6 };
int c[] = {3,4,5,6,7};
int* arr[3] = { a,b,c };
for (int i = 0; i < 3; i++) {
for (int j = 0; j < 5; j++) {
//printf("%d ", *(arr[i] + j));
printf("%d ", arr[i][j]);
}
printf("\n");
}
其中的*(arr[i] + j) 等价于 arr[i][j],但arr并不是一个二维数组,因为数组a,b,c的内存不一定是连续的,只是通过指针数组连接在一起而已。
8.字符指针
常见情况,char * 指向 char
char ch = 'w';
char *pc = &ch;
char * 指向 string / char[]
const char* pstr = "hello bit.";
上面代码的意思是把一个常量字符串的首字符 h 的地址存放到指针变量 pstr 中。
注意:
1.const 代表 *pstr 是不可修改的,因为"hello bit."在声明时就是一个常量字符串,要加const 修饰。
2.虽然pstr存储的是首字母 h 的地址,但也可以像访问char [] 一样操作 pstr
char * 与 char[] 的不同
#include <stdio.h>
int main()
{
char str1[] = "hello bit.";
char str2[] = "hello bit.";
const char *str3 = "hello bit.";
const char *str4 = "hello bit.";
if(str1 ==str2)
printf("str1 and str2 are same\n");
else
printf("str1 and str2 are not same\n");
if(str3 ==str4)
printf("str3 and str4 are same\n");
else
printf("str3 and str4 are not same\n");
return 0;
//output
//str1 and str2 are not same
//str3 and str4 are same
}
这里str3和str4指向的是一个同一个常量字符串。C/C++会把常量字符串存储到单独的一个内存区域,当几个指针指向同一个字符串的时候,他们实际会指向同一块内存。但是用相同的常量字符串去初始化不同的数组的时候就会开辟出不同的内存块。所以str1和str2不同,str3和str4不同。
9.数组名 与 &数组名
对于:
int arr[10];
arr是数组名,数组名表示数组首元素的地址。
&arr是数组的地址,而不是数组首元素的地址。
如下:
int arr[10] = { 0 };
int* p1 = arr;
int(*p2)[10] = &arr;
printf("arr = %p\n", p1); //arr = 0000005E20F8F608
printf("&arr= %p\n", p2); //& arr = 0000005E20F8F608
printf("arr = %p\n", arr); //arr = 0000005E20F8F608
printf("&arr= %p\n", &arr); //& arr = 0000005E20F8F608
printf("arr+1 = %p\n", p1 + 1); //arr + 1 = 0000005E20F8F60C
printf("&arr+1= %p\n", p2 + 1); //& arr + 1 = 0000005E20F8F630
printf("arr+1 = %p\n", arr + 1); //arr + 1 = 0000005E20F8F60C
printf("&arr+1= %p\n", &arr + 1); //& arr + 1 = 0000005E20F8F630
printf("%p - %p = %d\n", p1 + 1, p1, (int)(p1 + 1) - (int)p1);
//0000005E20F8F60C - 0000005E20F8F608 = 4
printf("%p - %p = %d\n", p2 + 1, p2, (int)(p2 + 1) - (int)p2);
//0000005E20F8F630 - 0000005E20F8F608 = 40
其实&arr和arr,虽然值是一样的,但是意义应该不一样的。
arr 是int*,arr+1,走四个字节。
&arr是 int[10], &arr+1 ,走4x10 = 40 个字节。
总结:即步长不同,访问权限也不同
10.数组指针
数组指针是指针,是能够指向数组的指针。例如:
int a[10] = {0};
int (*p)[10] = &a; //a是a[0]的地址, &a才是数组a的地址。
理解步骤:
1.因为是指针,所有p 首先要和 * 结合,故有 (*p) ,必须带括号,因为的优先级要高于 * 的,所以必须加上()来保证p先和 * 结合。
·
2.因为是数组指针,指向数组a,a有10个元素,故有[10].
·
3.因为数组a内存放的是int型变量,故有 int。
同理,指向指针数组的数组指针如下:
double* b[10] = {NULL};
double* (*p)[10] = &b;
使用:
#include<stdio.h>
void print_arr2(int(*arr)[5], int row, int col)
{
int i = 0;
int j = 0;
for (i = 0; i < row; i++)
{
for (j = 0; j < col; j++)
{
//printf("%d ", arr[i][j]);
printf("%d ", *(*(arr+i)+j));
}
printf("\n");
}
}
int main()
{
int arr[3][5] = { 1,2,3,4,5,6,7,8,9,10 };
//数组名arr,表示首元素的地址
//但是二维数组的首元素是二维数组的第一行
//所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址
//可以数组指针来接收
print_arr2(arr, 3, 5);
return 0;
}
假如arr[] [] = {arr0[], arr1[], arr3[]};
print_arr2(arr, 3, 5):
数组名arr,表示首元素的地址,但是二维数组的首元素是二维数组的第一行,所以这里传递的arr,其实相当于第一行的地址,是一维数组的地址,也就是arr0[]的地址,&arr0[].
printf("%d ", * ( * (arr+i)+j)):
arr是arr0[]的地址,arr+1 就是指针数组+1,直接跳过一个数组
*(arr+i)代表一维数组的首地址,i决定是那个一维数组。
*(arr+i)+j 代表一维数组的第j 个元素的地址。
*( *(arr+i)+j)) 代表一维数组的第j 个元素。
总结:
int arr[5]; int数组
int *parr1[10]; int指针数组
int (*parr2)[10]; int数组指针
int (*parr3[10])[5]
int (*parr3)[5] 是数组指针,int (*parr3[10])[5]是存放数组指针的数组
11.一维数组传参
void test(int arr[])//ok
{}
void test(int arr[10])//ok
{}
void test(int *arr)//ok
{}
void test2(int *arr[20])//ok
{}
void test2(int *arr[])//ok
{}
void test2(int **arr)//ok
{}
int main()
{
int arr[10] = {0};
int *arr2[20] = {0};
test(arr);
test2(arr2);
}
int arr[10] = {0};
test(arr);
arr[]是一维数组,传入一维数组,用一维数组接收,故void test(int arr[])和void test(int arr[10])每问题。同时,arr是数组的第一个元素的地址,是int的地址,故void test(int *arr)也行。
int *arr2[20] = {0};
test2(arr2);
arr2[]是一维指针数组,传入一维指针数组,用一维指针数组接收,故void test2(int * arr[20])和 void test2(int * arr[])可以。同时,arr2是数组的第一个元素的地址,是int * 的地址,故void test2(int **arr)也行。
12.二维数组传参
void test(int arr[3][5])//ok
{}
void test(int arr[][])//wrong
{}
void test(int arr[][5])//ok
{}
//总结:二维数组传参,函数形参的设计只能省略第一个[]的数字。
//因为对一个二维数组,可以不知道有多少行,但是必须知道一行多少元素。
//这样才方便运算。
void test(int *arr)//wrong
{}
void test(int* arr[5])//wrong
{}
void test(int (*arr)[5])//ok
{}
void test(int **arr)//wrong
{}
int main()
{
int arr[3][5] = {0};
test(arr);
}
int arr[3][5] = {0};
test(arr);
arr[][]是一个int二维数组,用二维数组接收没问题,故void test(int arr[3][5]) 和 void test(int arr[][5])可以。同时,arr是二维数组的首个一维数组的地址,是数组指针,故void test(int (*arr)[5])可以。
13.函数指针
函数声明(类似于数组声明):返回值类型 函数名 (参数类型),找到了 函数名 (参数类型),其余就是返回值类型。
函数指针:存放函数地址的指针。
#include <stdio.h>
void test()
{
printf("hehe\n");
}
int t2(int i){
}
int main()
{
printf("%p\n", test);
printf("%p\n", &test);
void (*p)() = test;
int (*p1)(int) = t2;
return 0;
}
对应函数来说,函数名 == &函数名,这点不同于数组。
void (*p)() = test:
函数指针,故*p
指向函数,()内存放参数类型,void代表函数返回值类型。
int (*p1)(int) = t2:
函数指针,故 *p1
指向函数,(int)存放参数类型, int代表函数返回值类型。
int add(int a, int b){
return a+b;
}
int (*p)(int, int) = add;
int ret = 0;
ret = add(2, 3);
ret = (*p)(2, 3);
ret = p(2, 3);
ret = (**p)(2, 3);
上面四个ret 的结果都是一样,对于函数指针来说,p = *p = **p = ***p = …,因为函数指针中, * 不起作用,多少个 * 都没有关系。
但下面不行,获得的是结果的地址
ret = *p(2, 3) //ret 存放的是结果的地址,即 5 的地址。
(*(void(*)())0)()
表示调用0地址处的函数,该函数无参,返回类型是void。
1.void(* p)(),p是函数指针,那么void( * )()就是函数指针类型
2.(void( * )())0 是对0进行强制类型转换,被解释为一个函数地址
3.* (void( * )())0 是对0地址进行了解引用操作
4.( * (void( * )())0)() 是调用0地址处的函数
void ( * signal(int,void( * )(int)))(int);
1.signal(): signal 和()先结合,说明signal是函数名
2. signal(int,void( * )(int)): signal函数的第一个参数的类型是int,第二个参数的类型是函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
3.去掉 signal(int,void( * )(int)) ,void ( * )(int) 表示其返回值,signal函数的返回类型也是一个函数指针,该函数指针,指向一个参数为int,返回类型是void的函数
综上,signal是一个函数的声明。
可以理解为 :
void ( *)(int) signal(int,void( * )(int)) ;//这是错误的
但这样的声明的不允许的。
例如:
typedef void (*) (int) pfun; // 这是错误的
typedef void (*pfun) (int); //√,对void(*)(int)的函数指针重命名为pfun
pfun signal(int,void( * )(int)); // 这样是允许的
14.函数指针数组
函数的地址存到一个数组中,那这个数组就叫函数指针数组,必须是同类型的函数指针.
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
#include <stdio.h>
int add(int a, int b)
{
return a + b;
}
int sub(int a, int b)
{
return a - b;
}
int mul(int a, int b)
{
return a*b;
}
int div(int a, int b)
{
return a / b;
}
int main()
{
int x, y;
int input = 1;
int ret = 0;
int(*p[5])(int x, int y) = { 0, add, sub, mul, div }; //转移表
while (input)
{
printf( "*************************\n" );
printf( " 1:add 2:sub \n" );
printf( " 3:mul 4:div \n" );
printf( "*************************\n" );
printf( "请选择:" );
scanf( "%d", &input);
if ((input <= 4 && input >= 1))
{
printf( "输入操作数:" );
scanf( "%d %d", &x, &y);
ret = (*p[input])(x, y);
}
else
printf( "输入有误\n" );
printf( "ret = %d\n", ret);
}
return 0;
}
15.指向函数指针数组的指针
void test(const char* str)
{
printf("%s\n", str);
}
int main()
{
//函数指针pfun
void (*pfun)(const char*) = test;
//函数指针的数组pfunArr
void (*pfunArr[5])(const char* str);
pfunArr[0] = test;
//指向函数指针数组pfunArr的指针ppfunArr
void (*(*ppfunArr)[5])(const char*) = &pfunArr;
return 0;
}
16.回调函数
回调函数就是一个通过函数指针调用的函数。如果你把函数的指针(地址)作为参数传递给另一个函数,当这个指针被用来调用其所指向的函数时,我们就说这是回调函数。回调函数不是由该函数的实现方直接调用,而是在特定的事件或条件发生时由另外的一方调用的,用于对该事件或条件进行响应。
十一、static关键字
总结:
①static关键字修饰局部变量不改变作用域,但是生命周期变长,
②static关键字修饰全局变量,不改变生命周期,作用域变小。
③函数被static关键字修饰后,函数只能在自己所在的源文件内部使用,不能在其他源文件内使用。
十二、sizeof单目运算符
参考:http://t.csdnimg.cn/05KIF
sizeof返回一个对象在内存中所占的存储空间,单位是字节byte。
特别是,sizeof(数组名)得到的结果是整个数组所占用的内存字节数。
十三、char与int的关系
它们的本质都是整数,无符号的char可以被当作无符号的整数来使用,而有符号的char可以被当作有符号的整数或字符来使用。
unsigned char c1 = 3;
char c2 = 3;
unsigned int i1 = 3;
int i2 = 3;
if (c1 = c2) {
printf("c1 == c2 ");
}
else {
printf("c1 != c2 ");
}
if (c1 = i1) {
printf("c1 == i1 ");
}
else {
printf("c1 != i1 ");
}
if (i1 = i2) {
printf("i1 = i2 ");
}
else {
printf("i1 = i2 ");
}
可以看出char和int 的本质都是 整数,可以比较。
十四、内存操作函数memcpy、memmove、memcmp、memset
参考:https://open.alipay.com/portal/forum/post/159601050
1.内存块拷贝函数memcpy
void * memcpy ( void * destination, const void * source, size_t num );
第一个参数的类型是无类型指针(void*),它指向拷贝的目的地内存块。就是你要把source放在destination里。
第二个参数的类型是被const修饰的无类型指针(void*),它指向拷贝数据的来源内存块。就是要拷贝的数据的地址。
第三个参数的类型是size_t(无符号整形),它表示要拷贝数据的字节数。就是以source为地址的数据的长度。单位是字节byte
例如:其中size_t是20,int是4个字节,就5个int,就是arr2 的前5个int copy到arr1 的前5位。
目标和源重叠就会出现问题!!!!
解决方法就是下面的memmove
2.移动内存块(可拷贝重叠内存块)memmove
具体实现操作可以看参考csdn
void * memmove( void * destination, const void * source, size_t num );
3.内存比较函数memcmp
int memcmp( const void * ptr1, const void * ptr2, size_t num );
在第一个相同的位置,ptr1的值小于ptr2 的值,返回 小于 0 的数。
在第一个相同的位置,ptr1的值大于ptr2 的值,返回 大于 0 的数。
例如:
4.内存设置函数 memset
每一个字节都变成了61,因为编译器给我们展示出来的是16进制,而字符’a’的ASCII码值为97,转换为16进制就是61,结果没问题。
十五、linux内存管理之malloc、kmalloc、kzalloc、vmalloc的区别
https://www.cnblogs.com/mz199/p/16332522.html
十六、编译如何指定头文件的路径、库文件的路径以及需要链接的库文件
十七、C++中头文件有无.h的区别
在C++中,#include 和 #include <iostream.h> 的主要区别如下:
- 标准与历史兼容性
<iostream>
:是现代C++标准库的头文件(无.h后缀),符合ISO C++标准,所有标准组件位于std命名空间中(如std::cout)。 <iostream.h>:是C++标准化前的旧头文件(带.h后缀),属于早期C++实现(如VC6.0),未引入命名空间,全局直接访问(如cout),现代编译器已弃用。 - 命名空间与语法
<iostream>
:必须显式使用std命名空间(如std::cout),或通过using namespace std声明 。
<iostream.h>:无命名空间,所有标识符全局可见,与现代C++的封装规范冲突