一、指针和数组
1、普通指针
指针是存储变量的内存地址,他有类型,地址没有类型。
int main(int argc, char const* argv[]) {
//定义变量
int i = 90;
//定义指针p,对变量i取地址符( &i)
int *p = &i;
float fi = 0.8f;
float *fp = &fi;
double j = 44.7;
//想通过四字节的指针复制给八字节的指针行不通,下面的是错误的
p = &j;
//空指针,访问内存地址NULL操作系统不允许,默认值是0
int *pp = NULL;
return 0;
}
2、二级指针
指针保存的是变量地址,保存这个变量还可以只指针变量。
int main(int argc, char const* argv[]) {
//定义变量
int i = 90;
//定义指针p,对变量i取地址符( &i)
int* p1 = &i;
int** p2 = &p1;
//加一个*是p1的地址,再加一个*是p1指针对应的值i
**p2 = 93;
printf("p1:%p,p2:%p\n", p1, p2);//【p1:0x7ffeef4145bc,p2:0x7ffeef4145b0】
printf("%d",i);
return 0;
}
3、数组指针
int main(int argc, char const* argv[]) {
//数组在内存中是连续的
int ids[] = {33, 44, 55, 66};
printf("%p\n", ids);
printf("%p\n", &ids);
printf("%p\n", &ids[0]);
//指针变量
int *p = ids;
printf("%d\n",*p);
printf("p的地址:%p\n", p);
//指针加法
p++;
printf("%d\n",*p);
printf("p的地址:%p\n", p);
return 0;
}
打印结果:
int main(int argc, char const* argv[]) {
//开辟连续的内存空间
int ids[5];
int i = 0;
for (; i < 5; i++) {
ids[i] = i + 10;
printf("%d\n", ids[i]);
}
printf("%d\n", *ids);
return 0;
}
打印结果:
4、函数指针
void msg(const char* title) {
printf("%s", title);
}
int main(int argc, char const* argv[]) {
// msg("xxx");
void (*func)(const char* msg) = msg;
func("函数指针");
return 0;
}
二、动态内存分配
栈内存:windows下,分配确定的常数2M,自动分配自动释放。
堆内存:程序员手动分配,可以分配操作系统的80%的内存,需要手动释放。
1、静态内存分配
//栈内存
void stackFun() {
int a[1024];
}
//堆内存
void heapFun() {
//开辟
void *p = malloc(1024 * 1024 * 10 * sizeof(int));
//释放
free(p);
}
//main函数
int main(int argc, char const* argv[]) {
stackFun();
heapFun();
return 0;
}
2、动态内存分配
数组的长度是动态的。
(1)重新分配内存:
//开辟
void* p = malloc(1024 * 1024 * 10 * sizeof(int));
//原来的内存不够用,需要重新分配,
void* p2 = realloc(p, sizeof(int) * 1024);
// p2分配的内存缩小,会导致数据丢失,p2分配的内存扩大内容,如果p后面扩展的有用,这连续扩展,还是原来的即p==p2,
//如果p后面存在了,分配的不连续,会将原来的数据复制到新的内存区域,则返回新的内存地址p!=p2,如果复制到新的内存区域还是不够放置
//数据,则返回NULL,开辟失败,原来的p还在
//这里只需要释放p2就可以了,p不需要释放
if (p2 == NULL) {
free(p2);
p2 = NULL;
}
(2)释放时候需要加上判断,给指针置空,标志释放成功
if (p == NULL) {
free(p);
p = NULL;
}
if (p2 == NULL) {
free(p2);
p2 = NULL;
}
(3)内存泄露问题 : P重新赋值之后在free,并没有释放
//40M
void* p1 = malloc(1024 * 1024 * 10 * sizeof(int));
free(p1);//重新赋值之前需要释放掉
//80M
void* p1 = realloc(p, 1024 * 1024 * 10 * sizeof(int)*2);
(4)不能多次释放
free(p);
free(p);
三、结构体的写法
1、方法一
struct Man {
int age;
char name[20];//表示字符数组,还可以char *name二者是有区别的
};
int main() {
//struct Man m1 = {20,"JSON"};
struct Man m1;
m1.age = 22;
strcpy(m1.name, "Rouse");
printf("%s,%d\n", m1.name, m1.age);
return 0;
}
如果是字符数组可以在声明时候赋值:
struct Man m1 = {20,"JSON"};
但是不能这样赋值:
m1.name = "JSON";
只能导入头文件#include <string.h>拷贝操作:
strcpy(m1.name, "Rouse");
char name[20]:可以修改里面的内容,但是不能修改字符串:name[0]='x'可以
char *name:可以修改字符串,但是不能修改里面的内容如:name++,*name=x不行
2、方法二:结构体变量
struct Man{
char name[20];
int age;
} m1,m2,m3;
m1,m2,m3为结构体的别名。
struct Man{
char name[20];
int age;
} Man *Man;
Man结构体变量,*Man结构体变量的指针。在JNI中发现很多别名的结构体和名称一样。可以做到类似于JAVA的Man.age = 20的样子。
3、方法三:全局赋值
struct Man{
char name[20];
int age;
} Man = {"Jack",20};
4、方法四:匿名结构体
struct {
char name[20];
int age;
} m1;
5、方法五:结构体嵌套
struct Teacher {
char name[20];
};
struct Student {
char name[20];
int age;
struct Teacher t;
};
// struct Student{
// char name[20];
// struct Teacher{
// int age;
// } t;
// };
int main(int argc, char const* argv[]) {
struct Student s1;
s1.age =20;
strcpy(s1.t.name,"json");
return 0;
}
6、结构体大小
结构体大小,字节对齐,大小为最大数据类型的整数倍,不够的话会自动填充,如下为16(8X2)不是12(8+4);
struct Man{
int a;
double b;
};
7、结构体别名
取别名:
typedef int Age;
Age a = 9;
结构体取别名Man;WP为指针类型的别名:
typedef struct Man{
char name[20];
} Man,*WP;
//简写也可以
typedef struct Man *wp;
typedef struct Man Man;
8、结构体函数指针成员
struct Girl {
const char *name;
int age;
void (*sayHi)(const char*);
};
void sayHi(const char* text) {
printf("%s\n", text);
}
int main() {
struct Girl g;
g.age = 20;
g.name = "dd";
g.sayHi = sayHi;
g.sayHi("hellp");
return 0;
}
9、结构指针的成员访问
typedef struct Girl {
const char* name;
int age;
void (*sayHi)(const char*);
} Girl;
void sayHi(const char* text) {
printf("%s\n", text);
}
typedef Girl* GirlP;
int main() {
//Girl为结构体别名,用typedef struct修饰,不需要加struct
Girl g;
g.age = 20;
g.name = "dd";
g.sayHi = sayHi;
g.sayHi("hellp");
//传递指针可以直接传递,传递变量新开辟内存空间
GirlP p = &g;
p->sayHi("999"); //C语言中使用 -> 操作符来访问结构指针的成员
return 0;
}
练习:
struct Song {
char name[20];
const char* singer;
} S, *SP;
int main(int argc, char const* argv[]) {
S.singer = "张三";
strcpy(S.name, "李四");
printf("singer:%s , name:%s\n", S.singer, S.name);
SP = &S;
printf("singer:%s , name:%s\n", SP->singer, SP->name);
return 0;
}
四、联合体
不同类型的变量共同占用一个内存,相互覆盖,互斥事件,目的是节省内存,大小为最大的成员所占用的字节数。
union MyTest {
int a;
int b;
double c;
};
int main(int argc, char const* argv[]) {
union MyTest t;
t.a = 1;
t.b = 2;
t.c = 3;
printf("%d,%d,%1f", t.a, t.b, t.c);
return 0;
}
打印结果,只有t.c有数(0,0,3.000000)
五、枚举
固定的数据和java一样。
enum Day { SUN, MON, SEN };
int main(int argc, char const* argv[]) {
enum Day d;
d = SUN;
return 0;
}
Day有默认值,相当于:
enum Day { SUN = 1, MON = 2, SEN = 3 };
六、define指令和宏定义
头文件:告诉编译器有这样一个函数,连接器负责找到这个函数
1、定义标识
#ifdef _cplusplus支持C++语法 endif
2、定义常数
#ifdef MAX 100;
没有类型,只是一个替换(宏定义),和java的static final不一样
int i = 99;
if (i < MAX){
printf("---小于100")
}
3、定义宏函数
#ifndef要和endif成对出现
void jd_cont_read(){
}
void jd_cont_write(){
}
//NAME是一个参数,当调用的时候会把NAME替换
#define jni(NAME) jd_cont_##NAME
//输出日志
#define LOG(LEVEL,FORMAT,...) printf(##LEVEL);printf(##FORMAT,__VA_ARGS_);
#define LOG_I(FORMAT,...) LOG("INFO:",##FORMAT,__VA_ARGS_);
#define LOG_D(FORMAT,...) LOG("DEBUG:",##FORMAT,__VA_ARGS_);
int main(int argc, char const *argv[]){
jni(write);//jd_cont_write,如果有参数jni(write)("参数");
LOG("%d%s","大小:",00)
LOG_I("大小:",00)
return 0;
}
防止循环引用:#pragma once