指针为什么要有类型?
1、类型表明指针指向的值是什么类型的,比如int类型的指针指向double类型的值,只能指向4个字节,类型告诉你读到哪里结束
2、地址只是开始的位置,类型告诉你读取到什么位置结束
3、指针有类型,地址没有类型
指针使用规范?
NULL空指针,其值为0,访问0X000000是不允许的,操作系统不允许。
多级指针(一般只使用到二级指针)
指针保存的是变量的地址,保存的这个变量还可以是一个指针变量。
int a = 90;
int *p1 = &a;
int **p1 = &p1;
指针运算
指针的加法,p++向前移动sizeof(数据类型)个字节
数组在内存中连续存储。指针的运算,一般在数组便利时才有意义,基于数组在内存中现行排列的方式。
指针函数
int add(int x, int y) {
return a + b;
}
int minus (int a , int b) {
return a - b;
}
//msg需要一个传递一个函数指针函数参数
void msg (int (*fun_p) (int a, int b)) {
int r = fun_p(a, b);
printf("%d", r);
}
int main() {
//加法
msg(add, 20, 20);
//减法
msg(minus, 50, 20)l
}
C语言内存分配
1、栈区(stack)
window下,栈内存分配2M(确定的常数),超出了,就会提示stack overflow错误,自动分配自动释放。
2、堆区(heap)
程序员手动分配释放,操作系统80%内存
在堆内存分配40M的空间,传递的参数是字节
int *p = malloc(1024*1024*10*sizeof(int));
//释放
free(p)
创建一个数组,动态制定数组的大小
//静态内存分配创建数组,数组的大小是固定的
int i = 10;
int a[i];
int len ;
scanf("%d", &len)
int *p = malloc( len * sizeof(int) );
//给数组元素辅助,(使用这一块刚刚开辟出来的内存区域)
int i = 0;
for (; i < len; i++) {
p[i] = rand() % 100;
}
//手动释放内存
free(p);
动态内存分配与静态内存分配的区别:
静态内存分配,分配内存的大小是固定的,问题:1、很容易超出栈内存的最大值。2、为了防止内存不够用开辟更多的内存,容易导致内存的浪费
动态内存分配,在程序的运行过程中,动态制定需要使用的内存大小,手动释放,释放之后,这些内存还能够被重新使用
int len ;
scanf("%d", &len)
int *p = malloc( len * sizeof(int) );
//给数组元素辅助,(使用这一块刚刚开辟出来的内存区域)
int i = 0;
for (; i < len; i++) {
p[i] = rand() % 100;
}
int addlen;
printf("增加数组的长度:")
scanf("%d", &addlen)
//内存不够用,扩大刚刚分配的内存
//1、原来内存的指针,2、内存扩大之后的总大小
int * p2 = realloc(p, sizeof(int)*(len+addlen) )
//重新分配
//省略
重新分配有两种情况
缩小,缩小的那部分数据丢失
扩大,可能是连续的,也可能不是连续的,如果没有足够空间,申请失败,原来的指针仍然有限
//相比malloc,更加好用
int *p = calloc( lens, sizeof(int) );
内存分配的几个注意细节
1、指针不能多次释放
2、释放完之后,指针要设置为NULL
3、内存泄漏(p重新赋值之后,再free,并没有真正释放内存)
int *p = malloc(1024*1024*10*sizeof(int));
*p = malloc(1024*1024*20*sizeof(int));
free(p)
//第一段内存区域泄露
3、全局区或静态区
4、字符常量区
5、程序代码区
字符串
char str[] = { 's', 'h','t', 't'}
char str[4] = { 's', 'h','t', 't'}
char str[10]= "china";
printf("%s\n", str);
printf("%#x\n", str); //输出地址
//可以修改
str[0]='s';
字符指针
内存连续排列,不可以修改,类似java中字符串
char *str = "hao are you";
使用指针加法,截取字符串
str += 3;
while (*str) {
printf("%c", *str);
str++;
}
打印 are you
strcpy () //赋值内容
strcat() //字符串拼接
strchr() //在一个字符串中查找给定字符的第一个匹配字符
strstr() //从字符串当中查找给定字符串的第一次出现的位置
结构体
结构体是一种构造数据类型
把不同的数据类型整合起来成为一个自定义的数据类型
struct Man{
char* name;
int age;
Int (*func)();
}
//初始化结构体的变量
//1、
strurc Man m1 = {"Jack", 21}
//2、
stuct Man m1;
m1.age = 23;
m1.name = "Rose";
如果在结构体的定义中,不是char* name;而是char name[20];赋值就应该使用strcpy(m1.name, "Jack")。第一种情况只能在声明的时候赋值,不能够重复赋值
结构体的几种写法
struct Man{
char name[20];
int age;
}m1; //m1结构体变量名
匿名结构体
//控制结构体变量的个数(限量版),相当于单例
struct {
char name[20];
int age;
}m1;
结构体的嵌套1
struct Teacher{
char name[20];
}
struct Student {
char name[20];
int age;
struct Teacher t;
}
struct Student s1 = new {"javk", 21, {"Jason"}}
结构体的嵌套2
struct Student {
char name[20];
int age;
struct Teacher{
char name[20];
}
}
结构体与指针
struct Man{
char name[20];
int age;
}
struct Man m1 = {"Javk", 30};
struct Man *p = &m1;
指针与结构体数组
struct Man{
char name[20];
int age;
}
struct Man mans[] = { { "Jack", 20 } , { "Rose", 20 } }
struct Man* p = mans;
for (; i < p + 2; i++ ) {}
int i = 0;
for (; i < sizeof(mans)/sizeof(struct Man); i++ ) {}
结构体的大小(字节对齐)
struct Man{
double weight;
int age;
}
打印占位16,
结构体变量的大小,必须是最宽基本数据类型的整数倍
//结构体与动态内存分配
struct Man *p = ( struct Man * )malloc (sizeof (struct Man) * 10);
typedef 类型取别名
1、不同的名称代表在干不同的事情 typedef int jint
2、不同的情况使用不同的别名
结构体函数指针成员
struct Girl {
char * name ;
int age ;
//函数指针
void (*sayHi) (char*);
}Girl ;
//Girl结构体指针取别名GirlP
typedrf Girl* GirlP; //别名又去取一个别名
//Girl结构体类似于Java中的类,name和age类似于属性,sayHi类似于方法
void (*sayHi) (char* text) {
}
补充:
字符数组赋值,只能在声明时
可以修改内容
重新赋值只能用strcpy()
char a[10] = "Happy";
a[0] = 'f';
strcpy(a, "Sad");
字符指针
char * a = "Happy";
a = "Dad";
不能够修改字符内容
联合体(共用体)
//不同类型的变量共同占用一段内存(相互覆盖),联合变量任何时刻只有一个成员存在,节省内存
//大小:最大的成员所占的字节数
union MyValue {
int x;
int y;
double z;
}
//最后一次赋值有效
枚举
//固定的数据
enum MyEnum {
One ;
Two;
Three;
Four;
Five
}
enum MyEnum e = Three;
//枚举的值,必须是括号中的值
//保证安全性
读取文本文件
char *path = "本地文件地址";
File *file = fopen(path, "r");
if (file == NULL) {
//打开失败
}
//读取
char buff[50];
while (fgets(buff, 50, file)) {
printf("%s", buff);
}
sclose(file);
写入文本文件
char *text = "aaaaaaaaa";
fputs(text, file) ;
//操作二进制文件(计算机的文件存储都是二进制)
//文本文件和二进制之分,其实是一个逻辑之分
//C读写文本文件与二进制文件的差别仅仅体现在回车换行符
//写文本时,没遇到‘\n’,会将其转换成‘\r\n’(回车换行)
//读文本时,没遇到‘\n’,会将其转换成‘\r\n’(回车换行)
char *path = "本地文件地址";
File *read_fp = fopen(path, "rb");
File *write_fp = fopen(path, "wb");
//复制
int buff[50]; //缓冲区域
int len = 0; //每次读到的数据长度
while ((len = fread(buff,sizeof(int), 50, read_fp))!=0) {
//将读到的内容写入新的文件
fwrite(buff, sizeof(int), len, write_fp);
}
fclose(read_fp);
fclose(write_fp);
获取文件的大小
char *path = "本地文件地址";
File *read_fp = fopen(path, "r");
//SEEK_END定位到文件末尾,0偏移量
fseek(fp, 0, SEEK_END);
//返回当前的文件指针,相当于文件开头的位移量
long filesize = ftell(fp);
C语言执行的流程
编译:形成目标代码(.obj)
连接:将目标代码与C函数库连接合并,形成最终的可执行文件
执行
预编译(预处理):为编译做准备工作,完成代码文件的替换工作。头文件告诉编译器有这样一个函数,连接器赋值找到这个函数的实现。也方便查找存在这样一个函数。
define指令
//1、定义表示
#ifdef __cplusplus 标识支持C++语法
//防止文件重复引入
//2、定义常数 (便于修改与阅读)
#define MAX 100
//3、定义“宏”
void com_add (int a, int b) {
}
void com_min(int a, int b) {}
#define jni(NAME) add_##NAME(); 参数替换成