1.指针是一种数据类型,装地址的数据类型。注意指针类型(对偏移,内存操作理解很重要),什么样的类型决定了操作空间的大小。地址加减,就是加减一个类型的大小。*代表间接访问运算符。
#include<stdio.h>
main()
{
//int a = 12;
//int *p = &a;//理解:首先声明一个指针类型的变量p、再取a的地址赋值给p。这里的*,起标记作用,标记p是一个指针变量
//p = &a;
//printf("% p %p %p %d\n", p, &a,&p,*p);//&p取p的地址,与前两个不同。p装的是a的地址.这里*是间接访问运算符或内存操作运算符
//a = 14;
//printf("%d\n", *p);//14 改变指向变量的值,也改变了指针的值
//*p = 145;
//printf("%d", a);//145 改变指针的值,也改变了指向变量的值
int a = 12;
int *p = &a;//切记:p是一块空间,有他自己的地址
int *p3 = p;
printf("%d\n", *p3);//输出12 ,p代表a的空间地址,*p3获取的就是a的值
//证明不同
//int *p1 = &p;// 报错:“p1” : “int *”与“int **”的间接寻址级别不同
int **p1 = &p;//二级指针。、,装一级指针地址
*p1 == p;//此时两者的关系 true
*p == a;
getchar();
return 0;
}
2.指针与数组二级指针与数组无关,涉及到数组与指针,利用好数组就是地址+[偏移量]的思维。指针数组(地址数组)(例:int p[5]):用数组装入地址的;数组指针(例:int (p)[5]):数组类型的地址**
#include<stdio.h>
int main(void)
{
int a[5] = { 9,6,4,5,4 };
int *p = &a[0];
printf("%p %p %p %p %p\n", p,p+1,p+2,p+3,p+4);//运算完p没有改变,用多条语句++p,也可以实现改变地址,但是会引起p的改变
printf(" %p %p\n", &*(p+2), &p[2]);
int i ;
for ( i = 0; i < 5; i++)
{
printf("%p\n", *(p + i));
//printf("%p\n", *p++);//注释上一句结果同上9 6 4 5 4,此语句p++改变了p的指向
printf("%d ,%d\n",a[i],p[i]);//指针的下标运算(指定首地址,且为连续的空间),两者结果相同 (地址+[偏移量])。
}
printf("%p %p %p %p %p %p\n", a, &a[0], &a[1], &a[2], &a[3], &a[4]);//a也是首地址,上下两者输出一样。
//**指针数组
int a1 = 1, b1 = 2, c1 = 3;
int *arr[3] = { &a1,&b1,&c1 };
printf("%d %p\n", *arr[1], arr[1]);//分别用来修改值,修改地址
//指针数组的拉链结构
int b11[3] = { 1,2,3 };
int c11[2] = { 2,5 };
int d11[4] = { 1,3,2,44 };
int e11[5] = { 45,6,5,4,26 };
int f11[2] = { 4,9 };
int *a11[5] = { b11,c11,d11,e11,f11 };
printf("a11:%d\n",a11[2][2]);//二维数组的方式访问,但是二维数组地址是连续的,一般拉链结构地址不连续,相互分开不同。大小不同但类型要同
//**数组指针
int a2[5] = { 3,4,2,6 ,8};
int*p2 = &a2[4];
//int *p22 = &a;//类型不兼容报错,根据报错找类型 warning C4047: “初始化”:“int *”与“int (*)[5]”的间接级别不同
//下标不一样,类型是不同的。int [6] int [5]不同类型的,可用上句测试
int(*p22)[5] = &a2;
//数组指针使用
p22[2];//为a2数组的地址,再偏移2(2个a2数组的大小,2*5*4)
printf("第二个:%d\n", (*p22)[2]);//输出2,取值设置等操作
printf("%p %p\n", &(*p22)[2],&a2[2]);
getchar();
return 0;
}
3.二维数组与指针。声明的指针指向哪个地址,操作的就是对应层次的地址。一个指针指向一个变量,p对应他本身(若p=&a[0][0]即*p==a[0][0])。指针的大小取决于编译环境。32位编译环境下的全为4字节,64位位8字节(vs编译器可以切换)。
#include<stdio.h>
int main(void)
{
//int a = 12;
//int *p = a;//警告:“int *”与“int”的间接级别不同
//printf("%p %d\n", p, *p);//输不出值
int a[2][3] = { {2,5,3} ,{3,5,3} };
int *p = &a[0][0];//==a[0]
for (int i = 0; i <= 5; i++)
{
printf("%d\n",*(p+i));//可以全部输出,因为存的空间是连续的
}
int (*p1)[3] = &a[1];
int(*p2)[2][3] = &a;//对比a[2][3]发现数组指针与数组一种形式上的关系。但是每一种声明的方式,指向层次都是相同的。可以和上一句对比
int i, j;
for ( i = 0; i < 2; i++)
{
for (j = 0; j < 3; j++)
{
printf("%d\n", (*p2)[i][j]);
}
}
getchar();
return 0;
}
4.堆区和栈区 内存分为:栈区(由系统申请),堆区(由自己申请释放),全局区,字符常量区,代码区。malloc申请一段连续的指定大小的空间,并返回首地址。
#include<stdio.h>
#include<stdlib.h>//两个都是malloc/calloc的库文件
#include<malloc.h>
#include<memory.h>
int main(void)
{
//void * maloc(size_t size)
int *p = (int*)malloc(4);//参数为字节数(除4得一,代表只有一个数),将空间标记为int*类型。4默认转为无符号的了,或者写成4u
p = (int*)malloc(4);//再申请的话会造成空间丢失,内存泄漏。但还是被占用的
//地址的类型决定了,地址每次访问的大小。size_t==unsigned int
//申请不到失败,返回NULL
*p = 2;//赋值
//空间赋值,字节设置值
memset(p, 0, 4);//memcpy才是数组赋值。参数1为赋值变量,参数二所赋的值,参数三字节数
if (NULL==p)
{
printf("申请失败!!");
}
int a = sizeof(size_t);//32位编译器环境是4字节,64位是8字节
printf("%p\n",p);
free(p);//释放空间,把空间还给操作系统
printf("%p\n", p);//释放后p就是野指针了(访问受限)。还有就是定义指针为初始化也是野指针
//malloc与数组
int *p1=(int*)malloc(sizeof(int) * 5);
int a1[5];
//int *p2 = &a1[0];
//p1 = p2;
/*for (int i = 0; i <= 5; i++)
{
printf("%d\n",*(p+i));
}*/
//free(p2);
//数组类型
int (*p2)[5]=(int(*)[5])malloc(sizeof(int) * 5);
(*p2)[5] = &a1;
printf("比较:%d\n",*p2==a1);//0
printf("比较1:%d\n", *p2 == a1);//1
for (int i = 0; i < 5; i++)
{
printf("%d\n", *( p2)[i]);
}
free(p2);
int(*p3)[2][5] = (int(*)[2][5])malloc(sizeof(int) * 5);
//calloc
int*p4=(int*)calloc(5, 4);//5个元素,每个4字节;申请数组比较方便
for (int i = 0; i < 5; i++)
{
printf("%d\n",p4[i]);//区别之一,全部初始化为0
}
free(p4);
//realloc重新修改字节
printf("%d\n", _msize(p));//_msize()查看malloc出的空间字节大小
int *p5 = (int*)realloc(p, 20);//重新分配20字节
getchar();
return 0;
}
5.函数基本使用
#include<stdio.h>
#include<malloc.h>
void fun1();//函数声明:先声明,函数体可以放最后
int fun2();
int fun4(int a, float b);
int fun5(int *p);
void fun6(int **p);
void fun(void)//无返回值,无参数。 整体放在最后面调用报错。使用函数声明来解决
{
printf("这是一个函数!");
return;//终止函数
}
//返回多个值
int * fun3(void)
{
int *p = (int*)malloc(8);
*p = 4;
p[1] = 5;
return p;//返回指针
//int p1[2] = { 4,5 };//虽然表面输出没问题,这是c自身的bug。会有警告
//return p1;//返回栈区,这里p1为栈区局部变量,运行完函数后,空间就被释放了,使用的是非法空间
}
int main(void)
{
//一般函数的执行略慢一丢丢于未封装的代码。因为涉及到跳转
fun();//调用:函数地址+参数列表
printf("%p %d %d\n", fun,&fun,fun==&fun);
(&fun)();//结果同上,不要忘记前面的小括号。
void(*p)(void) = fun;//函数指针
fun1();
printf("返回值:%d\n",fun2());
//返回多个值函数测试
int*a = fun3();
printf("%d %d\n",a[0],a[1]);
free(a);//记得释放堆空间
//通过函数修改外部值,思路修改地址,把地址传进函数
int b = 2;
int *p1 = &b;//p1代表(装载的)b的地址,p1自己还有一个自己空间的地址
fun5(p1);
//通过函数修改指针变量
fun6(&p1);
getchar();
return 0;
}
void fun1(void)//函数定义
{
printf("这是一个fun1!");
}
int fun2(void)
{
printf("无参有返回值函数!");
return 4;
}
int fun4(int a,float b)
{
printf("两参数 %d %d\n",a,b);
return 4;
}
int fun5(int *p) {
*p = 24;
printf("xiugao的%d\n",*p);
}
void fun6(int **p) {
*p = NULL;
}
5.较为特殊的函数和用法(数组参数,递归函数),递归注意递归控制变量
#include<stdio.h>
#include<stdarg.h>
//一维数组
void fun(int *p,int length)//int *p改为 int p[]([]可以写任何整数字),数组做形参会被解析成指针
{
for (int i = 0; i < length; i++)
{
printf("%d\n",p[i]);
}
}
//二维数组
void fun1(int (*p)[3],int hang,int lie)//同一维的改写,参数int p[任意][3]
{
for (int i = 0; i < hang; i++)
{
for (int j = 0; i < lie; i++)
{
printf("%d\n",p[i][j]);
}
}
}
//函数类型
int fun2() {}
int fun3(int a) {}
void fun4(int a) {};
//递归函数
int fun5(int n)
{
if (n==1 || n==2 || n==0)
{
return 1;
}
else {
return fun5(n - 1) + fun5(n - 2);
}
}
//指定未知参数个数a
void fun5(int a,...)
{
va_list ap;//定义参数数组
va_start(ap,a);//将参数装进数组
printf("%d\n", va_arg(ap, int));//将数取出来,就没了。再取就是 顺序往下取第二个
printf("%lf\n", va_arg(ap, double));
printf("%d\n", va_arg(ap, int));
}
int main()
{
int a[5] = { 1,2,3,4,5 };
fun(a, 5);
int a1[2][3] = { {1,2,3},{4,5,6} };
int(*p)[3] = a1;
fun(a1,2,3);
//函数地址 地址类型:有返回值类型,参数类型个数决定
//int ();对应的类型去掉名字,保留参数
//int (int a);
int(*p1)(int a) = fun3;//int a对应fun3的参数列表
//int (*p1)(int a) = fun4;//也是可以的
//递归斐波拉契数列,通项公式型
printf("递归:%d\n", fun5(6));
//多参数
fun5(3, 12, 34.34 , 78);//3是指参数个数
getchar();
return 0;
}
6.字符
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(void)
{
printf("%c\n",'A');//要用'',不然编译器会吧字母当做变量
printf("%c\n", 64);//输出字符@
printf("%d\n", 'A');//65
putchar('a');//输出
putchar('\n');
char a = 66;//1字节
printf("%c\n", a);//B
//scanf我们在控制台的输入,系统会开辟一块空间(输入缓冲区)存储我们的所有输入,按下回车结束
int d, f;
scanf("%d", &d);//从缓冲区拿数据,输入时2 3连续输,中间带空格可正常输出。回车后,这时scanf就去缓冲区取相应类型的数据
scanf("%d", &f);//去缓冲区读取数据
printf("%d %d\n", d, f);
//清空缓冲区 setbuf(stdin,NULL)(c标准)和fflush(stdin)(不标准,有些编译器不支持)还有while((c=getchar())!='\n'&&c!=EOF);
setbuf(stdin,NULL);
char e, g;
scanf("%c", &e);//字符类型的输入,回车和空格对scanf的读取有影响,输入一个字母回车。此时相当于存储了字母1和\n(回车)
scanf("%c", &g);//一般直接输入两个相连例如ab
printf("%c %c\n", e, g);//d存储为输入的字母,f存储的是\n
//scanf_s读取格式
scanf_s("%c \n", &e, 1);//1表示字节数,多个输入加多个指定的字节数
//char h =_getch();//随输入随读取,不需要回车确认。头文件conio.h
char c1 = 0;//转换大小写
while (1)
{
scanf("%c", &c1);
if(c1>=65&&c1<=90)
{
printf("%c",c1+32);
}
else if (c1 >= 97 && c1 <= 122)
{
printf("%c", c1 - 32);
}
else if (c1 == '\n')
break;
}
//字符数组
char arr[5] = { 'r','g','g','r','r' };
getchar();
return 0;
}
7.字符串(以\0 (0)结尾的字符数组)
#include<stdio.h>
#include<string.h>
int main(void)
{
char str[3] = { 'a','b','e' };//这是纯字符数组
char str1[3] = { 'a','b','\0' };//这就是字符串了
printf("%s\n", str);//输出乱码,因为结束不了
printf("%s\n", str1);//输出ab(遇到\0(0)结束,\0转义为0),不输出\0。用%c可以输出\0
printf("%s\n", "dfaf 522!!");//常量字符串,自带\0(dfaf 522!!\0)
char *p = "aaa555 55";//双引号返回字符串的首地址
char *pp = 'a';//
printf("指针输出字符%c\n", *p);
printf("指针输出字符地址%p \n", p);
//*p = 'w';//崩了,内存地址指向非法
//p [0]= 'w';//报错:写入位置 0x00966B44 时发生访问冲突。p指向的是一个常量区字符串地址,无法再进行修改。
//p = "wfwfgwg";//ok
//printf("指针输出字符串%s\n", *p);//直接炸掉
printf("指针输出字符串%s\n", p);
printf("查看*p %c\n", *p);//输出w
printf("我也可以输出!!");//定义中第一个参数为char* 而""刚好返回地址
char str11[15] = "1546 eege";//常量字符串不能修改。字符串在栈区数组和常量区都有
str11[1] = 'w';//改变的是栈区的
char str2[] = "vg1546 eege";//不用考虑越界
//str1[10]="wdqwfq";错误的不能一次转入。通过循环可以
char *p1 = "de";
int i = 0;
for (; *p!='\0'; p++)
{
str2[i] = *p;
i++;
}
str2[i] = '\0';//给结尾加个\0,不然就是替换前两个而已。
strcpy(str2, str11);//将str11赋值给str2(str2空间要足够大)
printf("%s\n", strcpy(str2, str11));
//strcpy()//类比于scanf_s(str,10,str1)多了个指定(参数1的)字节数
strncpy(str2, "fwfwgwg", 3);//3表示自从字符串中取三字节到str2中
strncpy_s(str2,10, "fwfwgwg", 3);//多了个指定参数1的字节数
char str22[20];
scanf("%s", str22);//输入字符串后面默认加了\0,输入的空格相当于一个分割符,不读空格。
//scanf("%s", str22,19);//保留一个字节给系统输入\0
//gets(str22);//读入全部,包括空格
//gets_s(str22,19);//总结_s系列的函数,主要优化了越界处理和报错,方便查找。在本句就报错,不再程序执行完在报错
printf("%s",str22);
char str3[3] = "谭";//字符串装入汉字两个字节,另外后面有个\0;或者百度汉字国标码
//求字符串长度
size_t a = strlen("156466");//只读取字符数,不读取\0. size_t无符号整形重命名
//字符串比较,切记与字符串长短无关,按顺序比较asc2码大小
int result = strcmp("abcdff", "abef");//前面的字符串大于后面的返回>0(两字符的差值),小于的返回小于0((两字符的差值),带符号),相等返回0;
//字符串拼接
strcat(str3, "f");
strcat(str3, "ferbe",1);//选一字节拼到str3
//将数字字符串转为整数
int aa = atoi("255");//头文件stdlib.h
int aa1 = atoi("gwg255");//返回0
int aa2 = atoi("255gree");//返回255
//整数转字符串
char stra[20] = { 0 };
itoa(235, stra, 2);//将235以二进制存于stra
//sprintf将后面的值全转为字符串存到stra里
sprintf(stra, "%d,%c,%f", 12, 'v', 12.3f);//不会输出到控制台,要用printf
//测试一些转义字符的字节数
printf("%u\n", strlen("\n"));
// \123 \0123 数字都为八进制的、\xf45为16进制的
//字符串数组
char *sta1[3] = {"fwef","regfwef","tjyy"};
getchar();
return;
}
8.结构体及数据存储。32位和4位cpu一次处理的数据分别为4字节,8字节。数据存储规则:字节对齐/内存对齐。
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
//手动设置字节对齐,这里表示每次存四字节
#pragma pack(4)
//在全局区定义则全部区域可用
struct Stu
{
void(*p)(void);//结构体不能定义函数,若要传入函数,就用指针
char name[10];
int age;
}stu1,stu2;//声明变量方式1
struct
{
struct Stu stuu;//结构体嵌套
char name[10];
int age;
}stu3={ "蔡徐坤",21 };//没名字的要在下面声明办理,不然其他地方难声明。这里初始化是全局的
void fun(void){}
struct Stu1
{
char name[10];
int age;
};
int main(void)
{
struct Stu student = {"蔡徐坤",21};//声明变量,初始化(按顺序)。外面的变量也可这样初始化,初始化就是全局的,这里的是局部的
//初始化指定元素struct Stu student = {.age=23 };
struct Stu *p = &student;
p->age;//结构体指针调用方式
(&student)->name;//用点会报错
strcpy((*p).name,"ysl");//设定定义字符串的值,不能直接用p->name="ysl"
strcpy(p->name, "ysl2");
student = (struct Stu) { "蔡徐坤2", 21 };//复合文字结构
struct Stu student1 = {fun};//传入函数的地址
//结构体数组
struct Stu student3[3] = { { "蔡徐坤3", 21 },{ "蔡徐坤4", 21 },{ "蔡徐5", 21 } };
//结构体大小也用sizeof(),计算规则依据于内存对齐
struct Stu1 student11 = { "蔡徐坤啊",21 };//
printf("%u %u\n",sizeof(struct Stu1), sizeof(student11));
//(顺序存储,读取顺序)计算规则:1.以最大类型为字节对齐宽度2.一次填补各个成员字节3.结尾补齐。另外数组的话,就看初始定义的大小char a[10],再根据基本类型中最大的(不是a[10])读取字节数取算
system("pause");
return 0;
}
9.联合体和枚举
#include<stdio.h>
#include<stdlib.h>
//联合类型,所有成员共用一块内存。修改一个变量其他的也会被改变
union Un
{
char c;
short s;
int i;
}un1;
//枚举 一组有名字的 int常数
enum Color {red ,black,white,blue=20};
int main(void)
{
printf("%p %p %p",&un1.c, &un1.s, &un1.i);//都一样
union Un un2 = {.i=34};//只那能初始化一个
enum Color co = 202;//大小就是4字节
printf("%d %d %d %d %d",red,black,white,blue,co);// 0 1 2
system("pause");
return 0;
}