c语言回顾

指针和变量

int *a; 定义int变量的地址(指针变量a)
int b; 定义变量b
&取地址符 *解引用 取地址引用的值
&b 变量b的地址
*a a内容为地址存放的变量
空指针不能直接赋值,要先赋予地址。

野指针指的是还没有初始化的指针
指针的范围和系统有关比如你的32位系统,指针长度为4,在64位操作系统下为8,变量也一样和系统有关具体用sizeof()判断字节。
在C语言中在main函数后定义函数,在使用前,需要在main函数前进行声明,否则会报错。

指针和数组

数组名本身就是一个指针(地址),它代表了数组的首地址,数组在内存中连续存放
指针+1或-1向上或向下移动sizeof(类型)个字节
sizeof(数据类型/变量名/指针变量名)//返回所占字节数 如sizeof(int)
指针变量和数据类型的大小编译器而异

数组与指针的关系

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
#include <stdio.h>
int main(void) {
int a[3]={5,6,7};
printf("%p\n",&a);
printf("%p\n",&a[0]);
printf("%p\n",&a[0]+1);
printf("%p\n",&a[1]);
printf("%s\n","----------------");
char b[3]={'c','d','e'};
printf("%p\n",&b);
printf("%p\n",&b[0]);
printf("%p\n",&b[0]+1);
printf("%p\n",&b[1]);
printf("%s\n","----------------");
int c[2][3]={{1,2,3},{4,5,6}};
printf("%p\n",&c);
printf("%p\n",&c[0][0]);
printf("%p\n",&c[0][0]+1);
printf("%p\n",&c[0][1]);
printf("%p\n",&c[0][0]+3);
printf("%p\n",&c[1][0]);
return 0;
}
//输出结果
0x7fff8a2f1d00
0x7fff8a2f1d00
0x7fff8a2f1d04
0x7fff8a2f1d04
----------------
0x7fff8a2f1cf0
0x7fff8a2f1cf0
0x7fff8a2f1cf1
0x7fff8a2f1cf1
----------------
0x7fff8a2f1d10
0x7fff8a2f1d10
0x7fff8a2f1d14
0x7fff8a2f1d14
0x7fff8a2f1d1c
0x7fff8a2f1d1c

数组的指针

例子 用数组的指针取数组的值

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
//一维数组
int i2[3] = { 11,22,33 };
//一维数组的指针
int(*p2) = i2;
//二维数组
int i1[2][3] = { {11,22,33},{44,55,66} };
int (*p1)[3] = i1;//二维数组指针 意思是 3个元素的一维数组的指针
//要获取 二维数组的55的话
int *i1_1 = *(p1 + 1);//把3个元素的一维数组的指针 推进一格到第二个数组的地址开头 可以传给一维数组指针接收
int i1_2 = *(i1_1 + 1);//一维数组指针移动一个位置 再解引用
printf("%d\n", i1_2);
printf("%d\n",*(*(p1+1) + 1));//省略写法

指针的数组

存放指针的数组

1
int *p[5];

分辨小技巧

最外边 * +变量名 加括号肯定就是指针

*+变量名后面带数组 没加括号就是数组

函数指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
void println(char *buffer) {
printf("%s\n", buffer);
}
//接受一个函数作为参数
void say(void(*p)(char*), char *buffer) {
p(buffer);
}
void(*p)(char*) = println;//函数指针 void是返回值 p是指针名 char* 是函数参数
p("hello");
//传递参数
say(println, "hello");
//类似java的回调函数
typedef void(*Callback)(int);
void test(Callback callback) {
callback("成功");
callback("失败");
}
void callback(char *msg) {
printf("%s\n", msg);
}
test(callback);

typedef

1
2
3
4
typedef unsigned char BYTE;
//在这个类型定义之后,标识符 BYTE 可作为类型 unsigned char 的缩写,例如:
BYTE b1, b2;

预处理器

预处理器是一个文本替换工具

预处理器 说明
#include 导入头文件
#if if
#elif else if
#else else
#endif 结束 if
#define 宏定义
#ifdef 如果定义了宏
#ifndef 如果未定义宏
#undef 取消宏定义

文本替换,每个使用到的地方都会替换为宏定义。
宏是在编译期间起作用,还没到运行期间,不会造成函数调用的开销(开辟栈空间,记录返回地址,将形参压栈,从函数返回还要释放堆
生成的目标文件大,不会执行代码检查

1
2
3
4
5
6
7
8
9
10
11
一般使用大写区分
#define TEST(i) i > 10 ? 1 : 0//宏函数
#define A 1//宏变量
int main()
{
printf("%d", TEST(11));
printf("%d",A);
system("pause");
return 0;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
//可变宏
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR,"NDK", __VA_ARGS__);
#define DN_INT(arg) int dn_ ## arg//连接符
int main()
{
DN_INT(i) = 10;//相当于 dn_ ## arg变成了 dn_i
dn_i = 100;//
system("pause");
return 0;
}
// \ 换行符
#define PRINT_I(arg) if(arg) { \
printf("%d\n",arg); \
}
//陷阱
#define MULTI(x,y) x*y
//获得 4
printf("%d\n", MULTI(2, 2));
//获得 1+1*2 = 3
printf("%d\n", MULTI(1+1, 2));

内联函数

1
2
3
4
inline int TEST(int i )
{
  return i > 10 ? 1 : 0;
}

区别

1、编译器会对内联函数的参数类型做安全检查或自动类型转换,而宏定义则不会,他只是进行单纯的文字替换,不会做任何类型的检查
2、内联函数在运行时可调试,而宏定义不可以。
3、内联函数可以访问类的成员变量,宏定义则不能
4、在类中声明同时定义的成员函数,自动转化为内联函数。

动态内存分配

静态内存是存放在栈里面的 动态内存是存放在堆里面的 函数结束出栈只释放静态内存
malloc.h 动态存储分配函数头文件,当对内存区进行操作时,调用相关函数.ANSI标准建议使用stdlib.h头文件,但许多C编译要求用malloc.h,使用时应查阅有关手册。一般来说stdlib.h包含malloc.h。

为什么需要动态内存,一般变量只在其方法终止后才会释放对应内存,而动态内存可以随意释放,更加节省内存。

malloc

没有初始化内存的内容,一般调用函数memset来初始化这部分的内存空间.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
#include <stdio.h>
#include <malloc.h>
int main(void) {
int * p = (int * )malloc(2*sizeof(int));//分配内存 这里静态分配了sizeof(p)个指针变量字节和动态分配了变量2*sizeof(int)个字节 若内存申请成功,函数返回申请到的内存的起始地址,若申请失败,返回NULL
memset(p, 0, sizeof(int));//将p指向的内存初始化为0 长度为一个int的长度
p [0] =1;//使用和数组一一样
*(p+1)=2;//也可以这样写
printf("%d\n",p[0]);
printf("%d\n",p[1]);
free(p);//把p指向的动态内存释放 注意此时p指针静态分配的内存并没释放
p=null;//把p指向静态分配的内存释放
return 0;
//输出结果
1
2
}

calloc

申请内存并将初始化内存数据为0

1
2
3
int * p = (int *)calloc(1,sizeof(int));//分配内存 这里静态分配了sizeof(p)个指针变量字节和动态分配了变量1*sizeof(int)个字节
free(p);//把p指向的动态内存释放 注意此时p指针静态分配的内存并没释放
p=null;//把p指向静态分配的内存释放

realloc

对动态申请的内存进行扩大调整

1
2
int * p = (int *)calloc(1,sizeof(int));
realloc(p, 2 * sizeof(int));//现在p动态分配了2*sizeof(int)个字节

alloca 栈申请内存

在栈申请内存,因此无需释放.

1
int *p = (int *)alloca(sizeof(int) * 10);

多级指针

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#include <stdio.h>
#include <malloc.h>
int main(void) {
int a=8;
int *b=&a;
int **c=&b;//指定存放int *的地址
int ***d=&c;//指定存放int **的地址
//*d相等于指针变量c
//*c相当于指针变量b
printf("%d\n",***d);
printf("%d\n",**c);
printf("%d\n",*b);
return 0;
//输出
8
8
8
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
#include <stdio.h>
#include <malloc.h>
void test (int **p){
*p=(int *)malloc(sizeof(int));
**p=9;
// int i=10;
// *p=&i; 这样写是错误的 i的地址在 test函数后会释放
}
void test2(int *p) {
*p = 12;//修改指针的值
}
int main(void) {
int *q;
test(&q);
int k = 2;
test2(&k);
printf("%d\n", k);
printf("%d",*q);
return 0;
//输出
9
}

常量

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//从右往左读
//P是一个指针 指向 const char类型
char str[] = "hello";
const char *p = str;
str[0] = 'c'; //正确
p[0] = 'c'; //错误 不能通过指针修改 const char
//可以修改指针指向的数据
//但是可以让p指向其它内容。
p = "12345";
//性质和 const char * 一样
char const *p1;
//p2是一个const指针 指向char类型数据
char * const p2 = str;
p2[0] = 'd'; //正确
p2 = "12345"; //错误
//p3是一个const的指针变量 意味着不能修改它的指向
//同时指向一个 const char 类型 意味着不能修改它指向的字符
//集合了 const char * 与 char * const
char const* const p3 = str;

可变参数

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
#include <stdarg.h>
double average(int num,...)
{
va_list valist;
double sum = 0.0;
int i;
/* 为 num 个参数初始化 valist */
va_start(valist, num);
/* 访问所有赋给 valist 的参数 */
for (i = 0; i < num; i++)
{
sum += va_arg(valist, int);
}
/* 清理为 valist 保留的内存 */
va_end(valist);
return sum/num;
}
int main()
{
printf("Average of 2, 3, 4, 5 = %f\n", average(4, 2,3,4,5));
printf("Average of 5, 10, 15 = %f\n", average(3, 5,10,15));
}

结构体

定义结构体有多种方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#include <stdio.h>
struct Student{
int age;
char sex;
};
int main(void) {
struct Student s1={12,'M'};
printf("%d----%c\n",s1.age,s1.sex);
struct Student s2;
struct Student *s3=&s2;
s2.age=18;
s3->sex='v';//相当于(*s3).sex
printf("%d----%c\n",s2.age,s2.sex);
OutPut(s3);
return 0;
}

共用体

共用里面的变量共用一个内存

1
2
3
4
5
6
union Data
{
int i;
float f;
char str[20];
} data;

字节对齐

1、某个变量存放的起始位置相对于结构的起始位置的偏移量是该变量字节数的整数倍;
2、结构所占用的总字节数是结构中字节数最长的变量的字节数的整数倍。

1
2
3
4
5
6
7
8
9
10
11
12
struct MyStruct1
{
short a; //占2字节 由于int的起始偏移要4的倍数 所以补两字节 最后占4字节
int b; //占4字节
short c; //占2字节 总字节数需要最长的变量的字节数的整数倍 所以补两字节 最后占4字节
};
struct MyStruct2
{
short a; //占2字节
short c; //占2字节
int b; //占4字节
};

枚举

1
2
3
4
5
6
7
emum Week{
A,B,C
};
....
....
emum Week wk=A;

字符串定义

1
2
3
4
char string[] = “hello world"; //栈(局部)
char *str = “hello world"; //文字常量区
char str1[50];
strcpy(str1,"haha");