1 整数的存储
- 只有整数才有原码,反码,补码,原码取反加一(除了符号位)得到补码。补码的补码会变成原码。 在任何位运算里,都是操作的补码,因为整数在内存里都是以补码存储的
2 移位运算符
- 移位运算符只能用与整数,且移动二进制位的补码,不改变这个数本身的值
- 左移,左边丢弃,右边补零
- 右移,分为逻辑和算数(一般)右移,与编辑器有关。1. 逻辑右移:左边⽤0填充,右边丢弃 2. 算术右移:左边⽤原该值的符号位填充,右边丢弃
- 在进行完位运算后,得到的仍然是补码。
3 位运算符
-
位操作符
&| ^ ~
-
& 有0则为0,同时为1则为1可以用真假来记
-
| 有1则为1,同时为0才是0
-
^ 相同为0,相异为1
-
~ 按位取反,包括符号位
-
异或的一些技巧
-
异或支持交换率,即与运算顺序无关
(3^3^5=3^5^3)
-
a^a=0 0^a=a
-
基于以上特点,可以用于交换两个变量但不使用第三个变量
-
int a = 5; int b = 10; a = a ^ b; b = a ^ b; a = a ^ b;
-
4 求一个正数在内存里存储的二进制中的1的个数
-
方法一:只能用于正数 int count = 0;int n = 15;while (n>0) {if (n % 2 == 1) {count++;}n /= 2;}printf("%d", count);方法二:一个数在&1之后,如果==1,则说明该位是1,否则则为0int count = 0;int n = -1;for (int i = 0; i < 32;i++) {if ((n >> i) & 1 == 1) {count++;}}printf("%d", count);方法三:n = n & (n - 1)这样写执行一次该代码,会去掉二进制里最右边的一个1(相当于将最右边的一个1拆成0……1)int count = 0;int n = -1;while (n != 0) {n = n & (n - 1);count++;}printf("%d", count);
5 逗号表达式
- (……,……,……)从左向右依次进行计算,整个表达式的结果是最后一个表达式的结果,常和while函数一起使用
6 结构体
- 创建结构体
struct stu {int age;char name[10];
}s2,s3;
- 初始化,使用{}
struct stu s1 = {10,"zhangsan"};
。也可以不按顺序初始化struct stu s4 = { .name = "lisi",.age = 20 };
- 可以用 . 访问成员
printf("%d", s1.age);
7 整型提升
- C语言中整型算术运算总是至少以(int)整型类型的精度来进行的。为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。(在 C 语言中使用
char
类型变量存储数字时,确实只能存储该数字二进制表示的前 8 位。) - 有符号整数提升是按照变量的数据类型的符号位来提升的
- 无符号整数提升,高位补0
- 在计算时提升,计算完在截断(char:只能在[-128,127])
- 在以%d 打印一个char类型的变量的时候,会先对该变量进行整型提升得到补码,在转化为原码进行打印(一般默认是有符号char)
8 算数转换
- 如果某个操作符的各个操作数属于不同的类型,那么除非其中⼀个操作数的转换为另⼀个操作数的类型,否则操作就无法进行。下面的层次体系称为寻常算术转换。
- 在比较有符号整型和无符号整型时,有符号整数会被隐式转换为无符号整数。转换的方式是保留二进制位不变,但是将其解释为无符号数。(例如,在 32 位系统中,有符号整数
-1
的二进制表示是0xFFFFFFFF
(补码形式)。当把它转换为无符号整数时,同样的二进制位0xFFFFFFFF
会被解释为无符号数4294967295
。)
9 指针
- 如果对一个(int)类型取地址,取到的是最小那个地址(一个int4个字节,每个字节都有自己对应的地址,&a则代表最小那一个地址)
- 任何数字存入指针变量里,都会被当作指针处理
- 一个指针变量在32位机器上是32个0/1组成的序列,需要4个字节来存储,在64位机器上是64个0/1组成的序列,需要8个字节来存储
- 不同的指针类型,在解引用时访问的字节数与指针类型有关
- 不同的指针类型,会影响+1的步长
10 void*指针
- 泛型指针可以理解为⽆具体类型的指针(或者叫泛型指针),这种类型的指针可以⽤来接受任意类型地址。但不能对该指针进行解引用和±操作。经常用于接受不同类型的函数参数,在使用时可以先强制类型转化后在使用。
11 指针运算
-
指针±整数: *(p+i)或者 scanf(“%d”, p + i),这里的p+i本身就表示一个地址
-
不只可以加上一个整数,还可以减去一个整数,在这个程序里,
int* p = &arr[9];
此时p里面存放的是最后一个元素的地址,也可以从这里向前访问。 -
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int* p = &arr[9]; for (i = 0; i < sz; i++) {printf("%d ", *(p - i)); }
-
-
指针-指针:
-
-
只有指向同一块区域的指针才可以相减(比如指向同一个数组的内存)
-
相减的结果的绝对值是两个指针之间的元素个数
-
int arr[10] = { 0 }; int n = &arr[0] - &arr[9]; printf("%d ", n);
-
-
指针的关系运算:(可以用这种关系运算来判断一个数组是否已经结束)
-
int arr[10] = { 1,2,3,4,5,6,7,8,9,10 }; int sz = sizeof(arr) / sizeof(arr[0]); int* p = arr; while (p < &arr[sz]) {printf("%d ", *p);p++; }
12 strlen函数的实现
方案一:(计数器)
size_t my_strlen(char* str){size_t num = 0;while (*str != '\0') {num++;str++;}return num;
}int main()
{char arr[] = "abcdef";size_t len = my_strlen(arr);printf("%zd", len);return 0;
}
方案二:(运用指针相减)
size_t my_strlen2(char* str) {char* start = str;while (*str != '\0')str++;size_t num = (size_t)(str - start);return num;
}
13 const修饰指针
-
对于
const int n = 10;
会让n变成常变量,无法被修改,但仍然是一个变量。const修饰的变量不能直接被修改,但可以通过指针侧面进行修改 const int n = 10; int* p=&n ; *p = 1; printf("%d ", n);如果不想让这个值被指针修改,只需要在int*前也加上const const int n = 10; const int* p=&n ; *p = 1; printf("%d ", n);
-
const int * p ,const放在左边,修饰的是整个(*p),也就是可以不能修改p指向的内容,但指针变量本身可以修改,即可以从指向a变为指向b
-
int * const p,const放在*的右边,修饰的是p本身,也就是不可以修改p本身,即p的指向,但可以通过p修改p指向的内容即 *p
14 野指针
-
这里面就造成p变成了一个野指针,因为在test结束的时候,n空间就释放了 int* test() {int n = 100;return &n; }int main() {int* p = test();printf("%d ", *p);return 0; }
15 assert断言
- assert包含在头文件
<assert.h>
里,如果在开头加上#define NDEBUG
会直接让程序里所有的assert断言都失效
int* p = &a; assert(p != NULL)
如果p为NULL,会直接终止程序- 该断言只有在debug模式里才有用,在release模式则会忽略该语句。
16 指针与数组
- 数组名是数组首元素的地址,但存在两个特殊情况
-
- sizeof(数组名) 在这种情况下,数组名表示整个数组,计算出的结果是整个数组的大小,单位是字节
- &数组名 这里的数组名也是表示整个数组,取出的是整个数组的地址(类型不是int*)
17 一维数组传参的本质
- 在实际以数组传参的时候,传递的就是一个地址,也就是如果数组经过传参,就彻底变成首元素的地址,没有特殊情况了,不可以用
sizeof(arr) / sizeof(arr[0])
来计算数组长度了。所以如果想知道数组的长度,必须在传参的时候传过去
18 冒泡排序
-
两两相邻的元素进行比较
-
一趟解决一个数字,所以需要(n-1)次
-
void bubble_sort(int* pa,int len) {for (int i = 0; i < len-1; i++) {//控制一共运行几次,如果有n个元素,只需要运行n-1次即可,因为最后一个元素会自行有序int flag = 0;for (int j = 0; j < len-1-i; j++) {//控制在一次循环里比较几对数据if (pa[j] > pa[j + 1]) {int tem = pa[j];pa[j] = pa[j + 1];pa[j + 1] = tem;flag = 1;}}if (flag == 0) {break;}} }//flag是为了优化代码,如果有一次外层循环一次都没有交换(flag==0),则说明已经有序,可以直接结束(break)
19 二级指针
-
int** p就是二级指针,int *说明p指向一个int *类型的数据,而第二颗 *则说明p是一个指针变量
-
int a = 10; int* pa = &a; int** ppa = &pa;
20 指针数组
-
存放指针的数组
-
模拟二维数组(与二维数组有不同,因为二维数组每一行之间是连续空间)
-
int main() {int a[3] = { 1,2,3 };int b[3] = { 4,5,6 };int c[3] = { 7,8,9 };int d[3] = { 10,11,12 };int* arr[4] = { a,b,c,d };//数组名是首元素地址for (int i = 0; i < 4; i++) {for (int j = 0; j < 3; j++) {printf("%d ", arr[i][j]);}printf("\n");}return 0; }
21 字符指针
-
在打印字符串的时候,只需要传入该字符数组首元素的地址
const char* p = "hdjslds";printf("%s",p);
-
字符指针可以被赋为字符数组,也可以直接用常量字符串赋值,两者的区别是常量字符串不可以被修改
-
char* p = "hdjslds";char arr[10] = "hdjslds"; char* p = arr;
-
对于同一个常量字符串,即使使用多个指针变量指向它,都指向的是同一块内存空间(即同一个常量字符串,因为它无法被修改,所以没必要创建多个)
-
const char* str1 = "hello"; const char* str2 = "hello"; if (str1 == str2) {printf("same"); }
22 数组指针变量
- 指向整个数组的指针
int(*p)[5]
其中(*p)说明p是一个指针变量,[5]说明p指向的是一整个数组,5代表元素个数,int 代表指向这个数组里边存放的元素的数据类型