#include
#include
#include
#include
typedef struct
{ int len;
int len1;
char data[0];//不占用内存空间,会在后面连续的分配一块内存空间使用,和指针区别开来
}SS1;
typedef struct
{ int len;
int len1;
int data[1];//占用内存空间,会在后面连续的分配一块内存空间使用,和指针区别开来
}SS2;
typedef struct
{ int len;
int len1;
char *data;//不占用内存空间,会在后面连续的分配一块内存空间使用,和指针区别开来
}SS3;
typedef struct
{ char len;
int len1;
char data;
}W0;
typedef struct
{ char len;
char len1;
int data;
}W1;
typedef struct
{ int len;
char len1;
short data;
double data1;
int len2;
}W2;
//#pragma pack(2)
/*结构体数据类型的对齐规则如下(常见陷阱)
1.数据类型自身的对齐值:
对于char型数据,其自身对齐值为1,对于short型为2,对于int,float,double类型,其自身对齐值为4,单位字节。
2.结构体或者类的自身对齐值:其成员中自身对齐值最大的那个值。
3.指定对齐值:#pragma pack (value)时的指定对齐值value。
4.数据成员、结构体和类的有效对齐值:自身对齐值和指定对齐值中小的那个值。
*/
#pragma pack(2)/* 设置2字节对齐 */
typedef struct
{
char len1;
long int data1;
int len2;
}W3,*b;
#pragma pack() /* 取消指定对齐,恢复缺省对齐(4字节对齐) */
typedef enum
{
Sunday,
Monday,
Tuesday,
Wednesday,
Thursday,
Friday,
Saturday //尾部不能有逗号
}weekday;
void testarr(int *m);
void teststatic();
void Show(short n);
void testarr(int *m)
{
printf("arr name m1 = %d\n", sizeof(m));
}
void teststatic()
{
static int n=10;
printf("n = %d\n", n);
n++;
}
void Show(short n) {
short i;
printf("%d(",n);
for(i = 0; i < 16; ++i)
printf("%d",(n & (1 << (15 - i))) >> (15 - i));
printf(")B\n");
}
int main(int argc, char **argv)
{
#if 0
/*1.C语言的输入和输出函数的用法*/
printf("--------------------------1------------------------------\n");
int a[10] = {0};
//a++;
printf("a=%d\n",a[0]);
//printf("a=%d\n",a);
//printf("a=%d\n",&a);
int b = 0;
scanf("%d",&b);
printf("%d\n",b);
/*2.利用结构体定义变长数组和指针的对比*/
printf("--------------------------2------------------------------\n");
typedef struct
{ int len;
int data[0];
}SS;
SS sa;//结构体相关的局部变量初始化,不初始化后面的数据是一个随机值
//SS sa={0};
memset(&sa,0,sizeof(SS));//清空以上结构体中的随机值
printf("sizeof(SS)=%d\n",sizeof(SS));
printf("len=%d\n",sa.len);
printf("data=%d\n",sa.data[0]);
sa.len = 10;
sa.data[0] = 80;
sa.data[10] = 80;
sa.data[20] = 80;
sa.data[50] = 80;
sa.data[60] = 80;
sa.data[65] = 80;
sa.data[66] = 80;//到了67就不行了
printf("len = %d\n",sa.len);
printf("data0 = %d\n",sa.data[0]);
printf("data10 = %d\n",sa.data[10]);
printf("data20 = %d\n",sa.data[20]);
printf("data50 = %d\n",sa.data[50]);
printf("data60 = %d\n",sa.data[60]);
printf("data60 = %d\n",sa.data[65]);
printf("data60 = %d\n",sa.data[66]);
printf("sizeof(SS) = %d\n",sizeof(SS));
printf("sizeof(sa) = %d\n",sizeof(sa));
//SS1 sa1={0};
printf("sizeof(SS1)=%d\n",sizeof(SS1));
printf("sizeof(SS2)=%d\n",sizeof(SS2));
printf("sizeof(SS3)=%d\n",sizeof(SS3));
/*3.数组名和指针的关系(重难点)*/
printf("--------------------------3------------------------------\n");
int *m = 0;
m=a;
printf("m = %d\n", *m);
a=m;//a的实质是一个常量,因此不能放在右边
#endif
/*4.数组名作为函数形参(退化成指针)*/
printf("--------------------------4------------------------------\n");
int m1[15] = {0};
printf("arr name m1 = %d\n", sizeof(m1));
/* 退化成指针 */
testarr(m1);
testarr(&m1[0]);
/*5.sizeof运算符(类型占内存的大小) 和 strlen()函数(计算字符串的长度) 区别*/
printf("--------------------------5------------------------------\n");
char str[20]="0123456789abcd";
int a = strlen(str);//a=10;int b=sizeof(str) str退化为指针
int b = sizeof(str);//而b=20
int i = 0;
printf("a = %d, b = %d\n", a, b);
/* 6. string:是一串以NUL字节结尾的字符集合*/
//while(NUL != str[i]) NUL需要定义和NULL(指针初值0在stdio.h定义指针初始化防止野指针很重要,局部变量在使用之前也一定要初始化)区别
printf("--------------------------6------------------------------\n");
while('\0' != str[i])
{
printf("STR the number of %d is %c\n",i,str[i]);
i++;
}
/*7.&(位运算结构是逐个位相与的值) &&(逻辑运算,结果是1 or 0只有两个结果 具有短路功能,只要第一个为false后面的就不在执行)*/
printf("--------------------------7------------------------------\n");
int x1 = 7 & 8;
printf("BIT calc number of %d\n",x1);
int x2 = 7 && 8;
printf("Logic calc number of %d\n",x2);
/*8.a指针常量(指针指向的地址不变,内容可变) b常量指针(指针指向的内存中的内容不变,地址可变)的区别*/
printf("--------------------------8------------------------------\n");
char* const pa = "abc";
//pa = "bcf";//非法
//*pa = 'p';非法,字符串常量不可以改变,数组定义的字符串可以修改,容易忽视!!!!!!
printf("pa content is %c\n",*pa);
char const * pb = "abc";
pb = "bcf";
//*pb = "efc";//非法,内容不可以变
printf("pb content is %c\n",*pb);
//特例
char const * const pc = "123abc"; //这个pc指向的地址和内容都不变,极端情况
printf("pc content is %c\n",*pc);//用于输出 指向的特定字符
printf("pc string content is %s\n" , pc);//用于输出整个字符串(原因????)
/*9.a指针函数(实质是一个函数,返回值是一个指针) b函数指针(函数名是一个指针)的区别*/
/*
//类型标识符 *函数名(参数表)
a.int* f(x,y); 指针函数
b.int (*f) (int x); //声明一个函数指针
f=func; //将func函数的首地址赋给指针f(回调函数的变相使用)
*/
/* 函数指针的应用实例------回调函数(写一个通用的数比较的回调函数,应用于不同的数据类型) 转换表(简化袖珍式计算器不用switch) */
printf("--------------------------分割开来------------------------------\n");
/* 数组指针(定义 int (*p)[n])
*()优先级高,首先说明p是一个指针,指向一个整型的一维数组,这个一维数组的长度是n,也可以说是p的步长。也就是说执行p+1时,p要跨过n个整型数据的长度
*
* 指针数组(定义 int *p[n])
*[]优先级高,先与p结合成为一个数组,再由int*说明这是一个整型指针数组,它有n个指针类型的数组元素
*
* */
printf("--------------------------9------------------------------\n");
/*10. static修饰局部变量的运用*/
printf("--------------------------10------------------------------\n");
/* 1.该静态局部变量n在全局数据区分配内存,而不是栈里面;
* 2.静态局部变量在程序执行到该对象的声明处时被首次初始化,即以后的函数调用不再进行初始化;
* 3.静态局部变量一般在声明处初始化,如果没有显式初始化,会被程序自动初始化为0;
* 4.它始终驻留在全局数据区,直到程序运行结束。但其作用域为局部作用域,当定义它的函数或语句块结束时,其作用域随之结束;*/
teststatic();
teststatic();
teststatic();
/*11. 逻辑移位 算数移位(算数左移和逻辑左移的结果一致,只有负数的两种右移的值不一样,
* 具体是逻辑右移还是算数右移 与编译器有关(通过这个可以判断编译器的类型,补1支持算数,补0逻辑,这个可以区别在C语言进行移植的时候作用重大),
* 从下面的结果来看 这个是逻辑数补充1的)*/
printf("--------------------------11------------------------------\n");
int h = -2;
int h1 = h >> 1;
int h2 = h >> 5;
int h3 = h >> 10;
printf("h1 = %d\n",h1);
printf("h2 = %d\n",h2);
printf("h3 = %d\n",h3);
/*char 类型的变量右移操作情况考虑越界和计算机内存的原反补码情况 可以用下面的几组值来确定本编译器是否默认为char有符号型*/
char sq = 153; //在C语言中默认的基础数据结构是signed ---char截取低八位1001101-----由于首位是1,因此是一个负数的补码(计算机中都是以补码存在,整数的原反补相同,负数的不同),移位之后1110011(负数补码-源码,符号位不变,后面的数据位取反加一)
printf("sq = %d\n",sq >> 4);//-7的原因在上面 解释(重要)
char sq1 = 127; //没有越界,可以
printf("sq1 = %d\n",sq1 >> 4);//7
char sq2 = 128; //同153
printf("sq2 = %d\n",sq2 >> 4);//-8
int h0 = -2;//-2的 有符号形右移(0x1111111111111110),右移超过1位之后时钟是-1,务必注意这一点(负数的逻辑右移是重点)
printf("左移时,末位补0,右移时,首位补1。\n");
Show(h0);
printf("左移4位后:");
Show(h0 << 4);
printf("右移4位后:");
Show(h0 >> 4);
short n1 = -1280;
printf("左移时,末位补0,右移时,首位补1。\n");
Show(n1);
printf("左移4位后:");
Show(n1 << 4);
printf("右移4位后:");
Show(n1 >> 4);
/* 12. 位运算经典实例常用 三种位操作*/
printf("--------------------------12------------------------------\n");
/*利用复合赋值符优势(value的值只计算一次,打字错误的可能性更小)把特定的位 设置为1 把特定的位设置为 0*/
int value = 0;
value |= 1 << 1; //特定位置1
printf("value = %d \n",value);
value &= ~(1 << 1);//特定位置0
printf("value = %d \n",value);
value = 3;
value &= 1 << 1; //提取特定位置1的值,其他位置清零
printf("value = %d \n",value);
/* 13. 左值 右值(左值意味着计算机内存中一个位置 右值意味着一个值 数组的名不能作为左值,左值可以作为右值使用,反过来是不可以的)*/
printf("--------------------------13------------------------------\n");
int a13 = 12;
int b13 = 13;
//b13 + 1 = a13;非法的右值作为左值
b13 = a13;
printf("b13 = %d \n",b13);
/* 14. 指针的地址和内容*/
printf("--------------------------14------------------------------\n");
/*非法使用,NULL解引用(*)和编译器有关,因此比较好的编程习惯是在使用*之前,首先判断指针 != NULL在使用,不然程序有潜在的危险
int *p = NULL;
printf("*p = %d \n",*p);
*/
//int g = 0;
//int *p = &g;
int *p = 0;
if (p != NULL)//指针解引用的规范用法
{
printf("*p = %d \n",*p);
}
//printf("p = %x \n",p);
/*15. 指针的指针 */
printf("--------------------------15------------------------------\n");
int p1 = 12;// p1作为左值的时候表示内存地址,存储12的位置,当做为右值是表示存储的内容12
int *p2 = &p1;
int **p3 = &p2;//指针的指针
//int **p4 = p2;//非法,
if (&p2 != p2)//&p2和&p1一样(指针变量p2和普通的变量p1都是变量,&表示得到p2在内存中的地址) p2 == &p1(该内存中存储的值),以上两种表达形式,左值右值的代表的含义不一样
{
printf("&p2 not same p2 \n");
}
printf("**p3 = %d \n",**p3);
//printf("**p4 = %d \n",**p4);
/*16. 指针的运算
* 以下三种运算都是基于同一个数组而言的,指针运算的前提,如果不是同一个数组,指针必然会指向我们不确定的位置
* a.指针的加法 指针+整数
* b.指针的减法 指针-指针
* c.指针的关系运算
*
* */
printf("--------------------------16------------------------------\n");
/*17. 指针越界 指向未知值的指针(指针错误的根源)*/
printf("--------------------------17------------------------------\n");
/*18. 计算结构体的大小
* 计算的两个规则:
* 一、起始位置(从0开始)为成员数据类型所占内存的整数倍(0也算),若不足则不足部分用数据将内存填充为该数据类型的整数倍。
* 二、结构体所占总内存为其成员变量中所占空间最大数据类型的整数倍
* 三、注意采用什么样的字节对齐方式来计算,重点关注w3结构体大小计算的了例子。
* */
printf("--------------------------18------------------------------\n");
printf("sizeof(W0)=%d\n",sizeof(W0)); //12
printf("sizeof(W1)=%d\n",sizeof(W1)); //8
printf("sizeof(W2)=%d\n",sizeof(W2)); //24
printf("sizeof(W3)=%d\n",sizeof(W3)); //10(2字节对齐) 12(4字节对齐情况)
printf("sizeof(b)=%d\n",sizeof(b)); //4,指针就是占4个字节
printf("sizeof(long)=%d\n",sizeof(long)); //4
printf("sizeof(long long)=%d\n",sizeof(long long)); //8
/*19. int的整型负数强制类型转化为无符号整型*/
printf("--------------------------19------------------------------\n");
char f0 = -2;
printf("f0 = %x\n",(unsigned int)((unsigned int) f0));
/*20. 指针 和 字符串 重要 字符串常量的不可变性*/
printf("--------------------------20------------------------------\n");
char *s0 = "mj";
/* 下面注释掉的表达式犯了两个错误1.把字符串存储到一个字符'm'的位置,内存溢出 2.以上即使是 *s = 'l',也不对,以上指针的
* 名命方式是字符串常量不可修改,如果想修改必须通过数组来名命*/
//*s0 = "like";
/* 区别下面的两种不同类型的输出 */
printf("s=%s\n" , s0);
printf("s=%c\n" , *s0);
/*21. C语言中常量的定义规则
*
* 1.整型
* 2.实型(注意后面的 E、e 类型作为常量的写法规则)
* 3.字符型
* 4.字符串型
*
* */
printf("--------------------------21.C语言中常量规则------------------------------\n");
//int ll = 1.23e1.5;后面的值典型的不可以作为常量,也不可以作为赋值,对于E、e类型有两个必须的条件:a.E/e的前面必须有数 b.E.e后面的数必须为整数
/*22. malloc动态内存分配和数组的区别
* 数组形式的优点是简单,但是缺点是:编译时申请内存,过大实际存储少,浪费,容易溢出。
* malloc执行的时候动态分配内存,注意使用之前判断是否申请成功。
* */
printf("--------------------------22------------------------------\n");
int kk[1000] = {0};
int *mk = (int*)malloc(sizeof(int)*1000);//注意强制类型转换
if (mk == NULL)
{
printf("动态内存申请失败\n");
}
/*23. 结构体相关知识总结
*
*
* 用于定义一个寄存器的值,比较长用的现象
* 结构体变量用 . 指针变量用 ->进行引用
*
* */
printf("--------------------------23------------------------------\n");
/*24. 联合体相关知识总结
*
* 联合的所有成员引用的内存中的相同位置
* 在一个成员长度不同的联合里,分配给联合的内存数量取决于它的最长成员长度。
* 利用联合法 和指针的强制类型转换的两种方法 判断大端小端(常规使用方法)
*
*
* 大端小端字节序的区别应用:
* 在网络编程里,网络字节序是big-endian的,而大部分的PC的系统都是X86处理器系列,
* X86采用的是little-endian,所以需要将网络数据流转换成本地数据流的话,需要进行字节序的转换。
*
*
*
* */
printf("--------------------------24------------------------------\n");
/* 指针法 */
int a24 = 1;
int *mm = &a24;
char *b24 = (char*) mm;
if (*b24 == 1)
{
printf("little end!\n");
}
else
{
printf("big end!\n");
}
/* 联合体法 :union型数据所占的空间等于其最大的成员所占的空间,对union型成员的存取都是相对于该联合体基地址的偏移量为0处开始,即,联合体的访问不论对哪个变量的存取都是从union的首地址位置开始。*/
union {
char m;
int n;
} f1 = {1};
if (f1.n == 1)
{
printf("little end!\n");
}else
{
printf("big end!\n");
}
/*25. 利用memset()初始化结构体
*
* 如果对一个结构体变量进行赋初值零时,采用枚举的方法对其所有元素赋零值是不正确的,
* 应该那些因数据对齐而产生的冗余空间并没有被清空,
* 所以应该采用memset()函数对该结构体变量所占用的内存空间一字节一字节的进行清空。
*
* 原型:extern void *memset(void *buffer, int c, int count);
* 功能:把buffer(是一个地址)所指内存区域的前count个字节设置成字符c。
*
* */
printf("--------------------------25------------------------------\n");
SS1 ss01;
memset(&ss01,0,sizeof(ss01));//注意是第一个参数是-------&ss01,不要错误的用ss01,易错点
printf("ss01.len = %d,ss01.len1 = %d,ss01.data[0] = %c,\n",ss01.len,ss01.len1,ss01.data[0]);
/*26. 数据的强制类型转换(常用)
*
*
*
*
* -1
* */
printf("--------------------------26------------------------------\n");
/* 1.float to int */
float ff = -1.5;
printf("ff = %d\n",(long int)(ff)); //强制类型转换 浮点到整形 损失精度
printf("ff = %d\n",(int)(round(ff)));//固定函数(同时采用的四舍五入)
/*将浮点型取整(4舍5入),和固定函数的执行效果一致*/
if (ff < 0)
{
printf("ff = %d\n",(int)(ff - 0.5));
}
else
{
printf("ff = %d\n",(int)(ff + 0.5));
}
/* 2.signed int to unsigned int 重点关注*/
/* 逻辑数就是编译器(代码表述)所使用的数据,物理数(补码)是逻辑数在具体CPU体系下的物理存储方式。区分以上两个概念
* C语言中的逻辑移位 物理移位
*
*
* 转换规则:
一、短数据类型扩展为长数据类型
1、有符号数向其它类型数据转化(如char到int,char到unsigned int)时,进行符号位扩展;无符号数向其它类型转换(如unsigned char到int, long)时,进行零扩展。
a)char x=10001001b; short y=x; 则y的值应为11111111 10001001b;
B)unsigned char x=10001001b; short y=x; 则y的值应为00000000 10001001b;
二、长数据类型缩减为短数据类型
由大数据类型向小数据类型转换时,保留低位字节153那个变换
三、同一长度的数据类型中有符号数与无符号数的相互转化
直接将内存中的数据赋给要转化的类型,数值大小则会发生变化。
另短类型扩展为长类型时,但短类型与长类型分属有符号数与无符号数时,
则先按规则一进行类型的扩展,再按本规则直接将内存中的数值原封不动的赋给对方。
* */
int q = -1;
unsigned int q1 = 0xffffffff;
printf("q = %x\n",(unsigned int)q);/* 有符号的数转化为同等长度的无符号的数---物理数不变,逻辑数可能变化 */
printf("q = %ld\n",(long int)q);/* 将int型数据送给long型变量时,其逻辑数保持不变,而物理数有所改变 */
printf("q1 = %d\n",(signed int)q1);/* 无符号的数转化为同等长度的有符号的数---物理数不变,逻辑数可能变化 */
/*27. 枚举使用
*
*
*
*
*
* */
printf("--------------------------27------------------------------\n");
weekday days = Sunday;
printf("days = %d\n",Sunday);
printf("days = %d\n",days);
return 0;
}