一.
1.一维数组的创建和初始化
int arr1[10];
int count = 10;
int arr2[count];// 不能创建数组
char arr3[10];
数组创建, [] 中要给一个常量才可以,不能使用变量。
char arr4[]="abc";
char arr5[3]={'a','b','c'};
双引号里面的是字符串,代表的却是一个指向无名数组起始字符的指针,该数组被双引号之间的字符以及一个额外的二进制值为零的字符‘\0’初始化;而单引号里面的代表字符,字符使用其ASCII码。
数组是使用下标来访问的,下标从0开始。
数组的大小可以计算得到。
2.一维数组在内存中的存储
随着数组下标的增长,元素的地址,也在有规律的递增。 由此可以得出结论:数组在内存中是连续存放的。
二维数组的创建和初始化
int arr[3][4] = { 1, 2, 3, 4 };// 长度为3的一维数组,每个元素又是长度为4
int arr1[3][4] = { { 1, 2 }, { 3, 4 } };
int arr2[][4] = { { 2.3 }, { 4, 5 } };
二维数组在内存中的存储
打印二维数组的每个元素
int main()
{
int arr[3][4];
int i = 0;
for(i=0; i<3; i++)
{
int j = 0;
for(j=0; j<4; j++)
{
printf("&arr[%d][%d] = %p\n", i, j,&arr[i][j]);
}
}
return 0;
}
数组名
数组名是数组首元素的地址
int arr[10] = {1,2,3,4,5};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
printf("%d\n", *arr)
两个例外: &arr 数组名出现在sizeof之后表示首元素地址
操作符和表达式
位操作符:
& //安位与
| //按位或
^ //按位异或
操作数必须为整数
不创建临时变量交换两个数的值
int a = 5;
int b = 6;
a = a^b;
b = a^b;
a = a^b;
printf("%d %d", a, b);
return 0;
逻辑操作符
&& 逻辑与
1&&1=1
1&&0=0
0&&0=0
|| 逻辑或
0||0=0
0||1=1
1||1=1
逗号表达式:
逗号表达式,就是用逗号隔开的多个表达式,从左往右依次执行,整个表达式的结果,是最后一个表达式的结果
整形提升:
C的整型算术运算总是至少以缺省整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
char a,b,c;
a=b+c;
b和c的值被提升为普通整型,然后再执行加法运算。加法运算完成之后,结果将被截断,然后再存储于a中。
如何进行整形提升
负数的整形提升:
正数的整形提升:
无符号整形提升,高位补0
整形提升的实例:
char a = 0xb6;
short b = 0xb600;
int c = 0xb6000000;
if (a == 0xb6)
{
printf("a");
}
if (b == 0xb600)
{
printf("b");
}
if (c == 0xb6000000)
{
printf("c");
}
a,b要进行整形提升,但是c不用,a,b整形提升之后就变成负数,所以a0xb6 b0xb600为假, c不发生整形提升所以 c==0xb6000000为真
实例二:
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(!c));
return 0;
}
表达式+c发生整形提升,所以是4个字节,-c也会发生整形提升,但是sizeof©就是一个字节。
结构体
结构体的定义和初始化:
struct point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct point p2; //定义结构体变量p2
结构体成员的访问:
typedef struct stu
{
char name[20];
int age;
}stu;
void print(struct stu* ps)
{
printf("name=%s,age=%d", (*ps).name, (*ps).age);
//使用结构体指针访问指向对象的成员
printf("name=%s,age=%d", ps->name, ps->age);
}
int main()
{
struct stu s= { "wangfan", 22 };
printf(&s); //结构体地址传参
system("pause");
return 0;
}
结构体传参:
struct S
{
int data[1000];
int num;
};
struct S s = { { 1, 2, 3, 4 }, 1000 };
void print1(struct S s)
{
printf("%d\n", s.num);
}
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s);
print2(&s);
system("pause");
return 0;
}
函数传参的时候,参数是需要压栈的。 如果传递一个结构体对象的时候,结构体过大,参数压栈的的系统开销比较大,所以会导致性能的下降。
结构体传参的时候,要传结构体的地址
结构体内存对齐
为什么要进行内存对齐:
1.平台原因(移植原因):
不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:
数据结构(尤其是栈)应该尽可能地在自然边界上对齐。
原因在于,为了访问未对齐的内存,处理器需要做两次内存访问;而对齐的内存访问仅需一次访问。
结构体内存对齐的规则:
1.第一个成员在结构体变量偏移量为0 的地址处。
其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。对齐数 = 编译器默认的一个对齐数与该成员大小中的较小值。vs中默认值是8 Linux默认值为4.
3.结构体总大小为最大对齐数的整数倍。(每个成员变量都有自己的对齐数)
4.如果嵌套结构体,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(包含嵌套结构体的对齐数)的整数倍。
struct S1
{
char c1;
char c2;
int i;
};
printf("%d\n", sizeof(struct S1));
char 为1个字节, int 为4个字节;char c1 从0偏移开始,占用一个字节;现在可用偏移为1偏移,接下来存放 int i ,1不是对齐数4 的整数倍,所以向后继续偏移一直到4,4是对齐数4的整数倍,所以将int i 存放在偏移地址为4处,占用4个字节;现在可用偏移变成8,存放 char c2 ,占一个字节,现处于9偏移。9不是最大对齐数4的整数倍,所以向后继续偏移直到偏移处为12的地方。
struct S2
{
double d;
char c;
int i;
};
printf("%d\n", sizeof(struct S2));
解析:
double 占8个字节, char 占一个字节,int 占4个字节;double d 从0处开始偏移,占用8个字节;现在可用偏移为8,接下来存放 char c ,8是对齐数1的倍数,所以char c 存放在偏移地址为8处,占用一个字节;现在可用偏移变成9,接下来存放 int i ,9不是对齐数4的倍数,所以继续向后偏移到12,12是对齐数4的倍数,所以将 int i 存放在偏移地址为12处,占用4个字节,现处于16偏移,是最大对齐数8的倍数。
结构体嵌套问题:
struct S3
{
char c1;
struct S2 s2;
double d;
};
printf("%d\n", sizeof(struct S3));
char 占一个字节,struct S2 s2 为例2 的结构体,占16个字节,double 占8个字节;char c1 从0处开始偏移,占用一个字节;现在可用偏移为1偏移;接下来存放struct S2 s2 ,1不是对齐数8的整数倍,所以继续向后偏移到8,8是对齐数8 的倍数,所以将结构体s2存放到8偏移处,占用16个字节;现在可用偏移为8+16=24,24是对齐数8的整数倍,所以将 double d 存放到当前的24偏移处,占用8个字节,现处于24+8=32偏移处,32是最大对齐数8的整数倍