C提高笔记(2):位运算、多维数组、结构体

1.位运算

1.1位逻辑运算符

4个位运算符用于整型数据,包括char.将这些位运算符成为位运算的原因是它们对每位进行操作,而不影响左右两侧的位,常规的位的逻辑运算符对整个值进行操作。
按位取反~:一元运算符 ~ 将每个1变为0,将每个0变为1,

~(10011010)         
01100101

位与(AND)&:二进制运算符&通过对两个操作数逐位进行比较产生一个新值。对于每个位,只有两个操作数的对应位都是1时结果才为1。

  (10010011) 
 & (00111101) 
 = (00010001)

C也有一个组合的位与-赋值运算符:&=。下面两个将产生相同的结果:

val &= 0377
val = val & 0377

位或(OR) |:二进制运算符|通过对两个操作数逐位进行比较产生一个新值。对于每个位,如果其中任意操作数中对应的位为1,那么结果位就为1.

	(10010011)
  | (00111101)
  = (10111111)

C也有组合位或-赋值运算符: |=

val |= 0377
val = val | 0377

位异或^
二进制运算符^对两个操作数逐位进行比较。对于每个位,如果操作数中的对应位有一个是1(但不都是1),那么结果是1。如果都是0或者都是1,则结果位0.

	(10010011)
  ^ (00111101)
  = (10101110)

C也有一个组合的位异或-赋值运算符: ^=

val ^= 0377
val = val ^ 0377

TipS:打开位:或|(或反|~ ) ,关闭位:与反&~
转置位用位异或运算符来转置

1.2移位运算符

左移 <<
左移运算符<<将其左侧操作数的值的每位向左移动,移动的位数由其右侧操作数指定。空出来的位用0填充,并且丢弃移出左侧操作数末端的位。在下面例子中,每位向左移动两个位置。

(10001010) << 2
(00101000)

右移 >>
右移运算符>>将其左侧的操作数的值每位向右移动,移动的位数由其右侧的操作数指定。丢弃移出左侧操作数有段的位。对于unsigned类型,使用0填充左端空出的位。对于有符号类型,结果依赖于机器。空出的位可能用0填充,或者使用符号(最左端)位的副本填充。

//有符号值
(10001010) >> 2
(00100010)     //在某些系统上的结果值
(10001010) >> 2
(11100010)     //在另一些系统上的结果
//无符号值
(10001010) >> 2
(00100010)    //所有系统上的结果值	
number << nnumber乘以2的n次幂
number >> n如果number非负,则用number除以2的n次幂

2.多维数组

2.1一维数组

元素类型角度:数组是相同类型的变量的有序集合
内存角度:连续的一大片内存空间
在C中,在几乎所有数组名的表达式中,数组名的值是一个指针常量,也就是数组第一个元素的地址。它的类型取决于数组元素的类型:如果他们是int类型,那么数组名的类型就是“指向int的常量指针”.
当数组名作为sizeof操作符的操作数的时候,此时sizeof返回的是整个数组的长度,而不是指针数组指针的长度。
当数组名作为&操作符的操作数的时候,此时返回的是一个指向数组的指针,而不是指向某个数组元素的指针常量。
*(arr + 3)表示arr指针向后移动了3个元素的长度,然后通过间接访问操作符从这个新地址开始获取这个位置的值。(arr + 3)等价于arr[3]

2.2 数组和指针

int a[10];
int *b;

声明一个数组时,编译器根据声明所指定的元素数量为数组分配内存空间,然后再创建数组名,指向这段空间的起始位置。声明一个指针变量的时候,编译器只为指针本身分配内存空间,并不为任何整型值分配内存空间,指针并未初始化指向任何现有的内存空间
因此,表达式a是完全合法的,但是表达式b却是非法的。*b将访问内存中一个不确定的位置,将会导致程序终止。另一方面b++可以通过编译,a++却不行,因为a是一个常量值

作为函数参数的数组名:当一个数组名作为一个参数传递给一个函数, 数组名其实就是一个指向数组第1个元素的指针,所以很明白此时传递给函数的是一份指针的拷贝。所以函数的形参实际上是一个指针, 为什么一维数组中无须写明它的元素数目了,因为形参只是一个指针,并不需要为数组参数分配内存。

int print_array(int *arr);
int print_array(int arr[]);

2.3多维数组

指向数组的指针(数组指针)
数组指针,它是指针,指向数组的指针。
数组的类型由元素类型和数组大小共同决定:int array[5] 的类型为 int[5];C语言可通过typedef定义一个数组类型:
定义数组指针有三种方式:

//方式一
void test01(){
	//先定义数组类型,再用数组类型定义数组指针
	int arr[10] = {1,2,3,4,5,6,7,8,9,10};
	//有typedef是定义类型,没有则是定义变量,下面代码定义了一个数组类型ArrayType
	typedef int(ArrayType)[10];
	//int ArrayType[10]; //定义一个数组,数组名为ArrayType
	ArrayType myarr; //等价于 int myarr[10];
	ArrayType* pArr = &arr; //定义了一个数组指针pArr,并且指针指向数组arr
	for (int i = 0; i < 10;i++){
		printf("%d ",(*pArr)[i]);
	}
	printf("\n");
}

//方式二
void test02(){
	int arr[10];
	//定义数组指针类型
	typedef int(*ArrayType)[10];
	ArrayType pArr = &arr; //定义了一个数组指针pArr,并且指针指向数组arr
	for (int i = 0; i < 10; i++){
		(*pArr)[i] = i + 1;
	}
	for (int i = 0; i < 10; i++){
		printf("%d ", (*pArr)[i]);
	}
	printf("\n");
}

//方式三
void test03(){	
	int arr[10];
	int(*pArr)[10] = &arr;
	for (int i = 0; i < 10; i++){
		(*pArr)[i] = i + 1;
	}
	for (int i = 0; i < 10; i++){
		printf("%d ", (*pArr)[i]);
	}
	printf("\n");
}

指针数组(元素为指针)

栈区指针数组
//数组做函数函数,退化为指针
void array_sort(char** arr,int len){
	for (int i = 0; i < len; i++){
		for (int j = len - 1; j > i; j --){
			//比较两个字符串
			if (strcmp(arr[j-1],arr[j]) > 0){
				char* temp = arr[j - 1];
				arr[j - 1] = arr[j];
				arr[j] = temp;
			}}}}

//打印数组
void array_print(char** arr,int len){
	for (int i = 0; i < len;i++){
		printf("%s\n",arr[i]);
	}
	printf("----------------------\n");
}
void test(){	
	//主调函数分配内存
	//指针数组
	char* p[] = { "bbb", "aaa", "ccc", "eee", "ddd"};
	//char** p = { "aaa", "bbb", "ccc", "ddd", "eee" }; //错误
	int len = sizeof(p) / sizeof(char*);
	//打印数组
	array_print(p, len);
	//对字符串进行排序
	array_sort(p, len);
	//打印数组
	array_print(p, len);
}
 堆区指针数组
//分配内存
char** allocate_memory(int n){	
	if (n < 0 ){
		return NULL;
	}
	char** temp = (char**)malloc(sizeof(char*) * n);
	if (temp == NULL){
		return NULL;
	}
	//分别给每一个指针malloc分配内存
	for (int i = 0; i < n; i ++){
		temp[i] = malloc(sizeof(char)* 30);
		sprintf(temp[i], "%2d_hello world!", i + 1);
	}
	return temp;
}

//打印数组
void array_print(char** arr,int len){
	for (int i = 0; i < len;i++){
		printf("%s\n",arr[i]);
	}
	printf("----------------------\n");
}

//释放内存
void free_memory(char** buf,int len){
	if (buf == NULL){
		return;
	}
	for (int i = 0; i < len; i ++){
		free(buf[i]);
		buf[i] = NULL;
	}
	free(buf);
}

void test(){	
	int n = 10;
	char** p = allocate_memory(n);
	//打印数组
	array_print(p, n);
	//释放内存
	free_memory(p, n);
}

小结:

  • 在绝大多数表达式中,数组名的值是指向数组第1个元素的指针。这个规则只有两个例外,sizeof和对数组名&。
  • 指针和数组并不相等。当我们声明一个数组的时候,同时也分配了内存。但是声明指针的时候,只分配容纳指针本身的空间。
  • 当数组名作为函数参数时,实际传递给函数的是一个指向数组第1个元素的指针。我们不单可以创建指向普通变量的指针,也可创建指向数组的指针。

3.结构体

结构体类型的定义
struct Person{
	char name[64];
	int age;
};
typedef struct _PERSON{
	char name[64];
	int age;
}Person;

结构体变量的定义
struct Person{
	char name[64];
	int age;
}p1; //定义类型同时定义变量
struct{
	char name[64];
	int age;
}p2; //定义类型同时定义变量
struct Person p3; //通过类型直接定义

结构体变量的初始化
struct Person{
	char name[64];
	int age;
}p1 = {"john",10}; //定义类型同时初始化变量
struct{
	char name[64];
	int age;
}p2 = {"Obama",30}; //定义类型同时初始化变量
struct Person p3 = {"Edward",33}; //通过类型直接定义
结构体成员的使用
struct Person{
	char name[64];
	int age;
};
void test(){
	//在栈上分配空间
	struct Person p1;
	strcpy(p1.name, "John");
	p1.age = 30;
	//如果是普通变量,通过点运算符操作结构体成员
	printf("Name:%s Age:%d\n", p1.name, p1.age);
	//在堆上分配空间
	struct Person* p2 = (struct Person*)malloc(sizeof(struct Person));
	strcpy(p2->name, "Obama");
	p2->age = 33;
	//如果是指针变量,通过->操作结构体成员
	printf("Name:%s Age:%d\n", p2->name, p2->age);
}

注意:定义结构体类型时不要直接给成员赋值,结构体只是一个类型,编译器还没有为其分配空间,只有根据其类型定义变量时,才分配空间,有空间后才能赋值。

结构体赋值
相同的两个结构体变量可以相互赋值,把一个结构体变量的值拷贝给另一个结构体,这两个变量还是两个独立的变量。

深拷贝和浅拷贝
//一个老师有N个学生
typedef struct _TEACHER{
	char* name;
}Teacher;
void test(){	
	Teacher t1;
	t1.name = malloc(64);
	strcpy(t1.name , "John");
	Teacher t2;
	t2 = t1;
	//对手动开辟的内存,需要手动拷贝
	t2.name = malloc(64);
	strcpy(t2.name, t1.name);
	if (t1.name != NULL){
		free(t1.name);
		t1.name = NULL;
	}
	if (t2.name != NULL){
		free(t2.name);
		t1.name = NULL;
	}
}
结构体数组
struct Person{
	char name[64];
	int age;
};
void test(){
	//在栈上分配空间
	struct Person p1[3] = {
		{ "John", 30 },
		{ "Obama", 33 },
		{ "Edward", 25}
	};
	struct Person p2[3] = { "John", 30, "Obama", 33, "Edward", 25 };
	for (int i = 0; i < 3;i ++){
		printf("Name:%s Age:%d\n",p1[i].name,p1[i].age);
	}
	printf("-----------------\n");
	for (int i = 0; i < 3; i++){
		printf("Name:%s Age:%d\n", p2[i].name, p2[i].age);
	}
	printf("-----------------\n");
	//在堆上分配结构体数组
	struct Person* p3 = (struct Person*)malloc(sizeof(struct Person) * 3);
	for (int i = 0; i < 3;i++){
		sprintf(p3[i].name, "Name_%d", i + 1);
		p3[i].age = 20 + i;
	}
	for (int i = 0; i < 3; i++){
		printf("Name:%s Age:%d\n", p3[i].name, p3[i].age);
	}
}

结构体成员偏移量
结构体字节对齐
内存的最小单元是一个字节,当cpu从内存中读取数据的时候,是一个一个字节读取。
提高存取数据的速度。比如有的平台每次都是从偶地址处读取数据,对于一个int型的变量,若从偶地址单元处存放,则只需一个读取周期即可读取该变量;但是若从奇地址单元处存放,则需要2个读取周期读取该变量。
某些平台只能在特定的地址处访问特定类型的数据,否则抛出硬件异常给操作系统。
对于标准数据类型,它的地址只要是它的长度的整数倍。
对于非标准数据类型,比如结构体,要遵循一下对齐原则:

  1. 数组成员对齐规则。第一个数组成员应该放在offset为0的地方,以后每个数组成员应该放在offset为min(当前成员的大小,#pargama pack(n))整数倍的地方开始(比如int在32位机器为4字节,#pargama pack(2),那么从2的倍数地方开始存储)。
  2. 结构体总的大小,也就是sizeof的结果,必须是min(结构体内部最大成员,#pargama pack(n))的整数倍,不足要补齐。
  3. 结构体做为成员的对齐规则。如果一个结构体B里嵌套另一个结构体A,还是以最大成员类型的大小对齐,但是结构体A的起点为A内部最大成员的整数倍的地方。(struct B里存有struct A,A里有char,int,double等成员,那A应该从8的整数倍开始存储。),结构体A中的成员的对齐规则仍满足原则1、原则2。

手动设置对齐模数:
#pragma pack(show)
显示当前packing alignment的字节数,以warning message的形式被显示。
#pragma pack(push)
将当前指定的packing alignment数组进行压栈操作,这里的栈是the internal compiler stack,同时设置当前的packing alignment为n;如果n没有指定,则将当前的packing alignment数组压栈。
#pragma pack(pop)
从internal compiler stack中删除最顶端的reaord; 如果没有指定n,则当前栈顶record即为新的packing alignement数值;如果指定了n,则n成为新的packing alignment值
#pragma pack(n)
指定packing的数值,以字节为单位,缺省数值是8,合法的数值分别是1,2,4,8,16。

#pragma pack(4)
typedef struct _STUDENT{
	int a;
	char b;
	double c;
	float d;
}Student;

typedef struct _STUDENT2{
	char a;
	Student b; 
	double c;
}Student2;

void test01(){
	//Student
	//a从偏移量0位置开始存储
	//b从4位置开始存储
	//c从8位置开始存储
	//d从16位置开存储
	//所以Student内部对齐之后的大小为20 ,整体对齐,整体为最大类型的整数倍 也就是8的整数倍 为24
	printf("sizeof Student:%d\n",sizeof(Student));

	//Student2 
	//a从偏移量为0位置开始 
	//b从偏移量为Student内部最大成员整数倍开始,也就是8开始
	//c从8的整数倍地方开始,也就是32开始
	//所以结构体Sutdnet2内部对齐之后的大小为:40 , 由于结构体中最大成员为8,必须为8的整数倍 所以大小为40
	printf("sizeof Student2:%d\n", sizeof(Student2));
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值