C高级
- 指针的应用场景
- 函数返回多个值的时候
- 函数返回运算状态,结果通过指针返回,一般都是让函数返回特殊值(比如 0 和 -1 )标识函数的状态,真正的返回值传一个参数来表示。
- 指针可以相减,相减的值为
字节 / sizeof(对应的类型),也就是两个指针相隔的元素的个数
day6
[1]变量类型(32位操作系统)
- 基本类型
- 字符类型 char、signed char、unsigned char 占一个字节
- 整形: short 2字节、int 4字节、long 4字节、long long 8字节、short int (即shrot)、long int(即long)、long long int(即long long)
- 打印用
- %d
- %u 无符号
- %ld
- %lld
- %#o(八进制) 八进制数,带前缀
- %#x(十六进制) 十六进制数,带前缀
- %02d 占2格,没有占满的在前面用0填充
- %4d 占4格,没有占满的在前面用空填充,右对齐
- %-4d 在后面用空填充,左对齐
- 实型:
- float 4字节 ,%f
- double 8字节 %lf
- long double 12字节 %Lf
- %2.2f,%g显示小数点后的有效位。%e,指数形式显示浮点数
- 枚举类型:enum
- 构造类型
- 数组 char a[10],a的类型为 char [10] int t[20] t 的类型为 int [20]
- 结构体 struct
- 共用体 union
- 指针类型:所有的指针在32位系统中都是4字节,64位系统中都是8字节
- char * p;
- int * t;
- long * w;
- short * r;
- void * t;
- 空类型
- void function(void)
[2]控制语句
- if else
- switch case. break default
- do while. while
- for
- goto
- continue;
- return;
[3]运算符
单算移关与,异或逻条赋,逗
- 单目运算符:+ - ++ -- ~ *
- 算数运算符:+ - * / %
- 移位运算符:<< >>
- 关系运算符:> < == != <= >=
- 与:& 10&7 = 1010 & 0111 = 0010
- 异或: ^ 0^0 = 0 0^1=1 1^1=0
- 或:| 0|1 = 1 0|0 = 0 1|1 = 1
- 逻辑运算符:&& || !
- 条件运算符(三目):? :
- 赋值运算符:= += -= *= /= %= <<= >>=
- 逗号运算符:, b = (3.,4)
注意:运算顺序
- 单条赋:运算方向从右向左,
- 其他:运算方向从左向右。
[4]一级指针
-
char a[] = "hello world"; char * p = a; printf("a = %d, *p = %d\n", a, *p); printf("&a = %p, p = %p, &p = %p\n", &a, p, &p);
[5]指针和一维数组
-
char a[10] = {'h','e}; a 是数组名,他是数组的首地址,他是常量,所以不能做a++,也不能做赋值操作 a = 50;
-
char * p; p是指针变量,占4字节,它指向一个字符的地址
-
p = a; p = *a; //错误 p = &a //错误 -
示例
#include <stdio.h> int main(void) { char a[] = "hello world"; char * p; p = a; printf("&p = %p\n", &p); //结果 &p = 0x7ffffc41d2d0 printf("*p = %c, p = %p\n", *p, p); // //结果 *p = h, p = 0x7ffffc41d2dc printf("&p + 1 = %p\n", &p + 1); //h +8 //结果 &p + 1 = 0x7ffffc41d2d8 加8,因为64位系统中指针就是8个字节 printf("&a[0] = %p\n", &a[0]); //和p地址一样 //结果 &a[0] = 0x7ffffc41d2dc printf("*(&a[0] + 1) = %c, &a[0] + 1 = %p\n",*(&a[0] + 1), &a[0] + 1); //等同p + 1 //结果 *(&a[0] + 1) = e, &a[0] + 1 = 0x7ffffc41d2dd printf("a = %p, a + 1 = %p, *(a + 1) = %c\n", a, a + 1, *(a + 1)); //结果 a = 0x7ffffc41d2dc, a + 1 = 0x7ffffc41d2dd, a + 1 = e printf("&a = %p, &a + 1 = %p\n", &a, &a + 1); //这里加上的是数组的长度,该示例中数组长度为12,所以这里加12 //结果 &a = 0x7ffffc41d2dc, &a + 1 = 0x7ffffc41d2e8 return 0; }
[8]关于指针 +1 移动的问题
-
+1 和 取值( * ) 都是降维,取地址符( & ) 是升维,然后看他是什么类型,他就移动对应类型大小
-
例如:char * p
- &p+1 :本来p是一个数组指针,+1 降维变成一个char的元素, &升维回到原来的数组指针,指针大小在32位系统里面为4,在64系统中为8,所以他就是加4或者加8
- a+1 :本来a是一个数组名,是第0个元素的首地址, +1降维,变成一个char的元素,char类型大小为1,所以加1字节
- &a+1 :本来a是一个数组名, +1 降维, &升维变回数组,开头定义了
char a[] = "hello world",a 数组里面有 "hello" 加空格符加world最后加上字符串的空指针\0,一共是12个字节,所以最后加12个字节
-
char a[] = "hello world"; char * p; p = a; *p++; //单目运算符,从右到左,所以先执行p++,后执行*,但是p++是后运算,所以先使用p,然后进行*p,最后进行p++ *(p++); //和上述一样 (*p)++: //*(p) = *(p) + 1,*p为'h'字符,加一变成1,他会先打印'h',然后把*p的值加1 *++p; //先++p,移动指针,然后在取里面的值
例题:输入一个字符串到一个数组里面,然后利用指针来让数组里面的字符前后调换位置
#include <stdio.h>
#include <string.h>
char *s_gets(char [], int);
void changeString(char *, char *);
int main(void)
{
char a[100] = "";
char * p, * q;
puts("Please enter a string.");
s_gets(a, 100); //老师这里用scanf("%[^\n]", a);来代替,也是一样的
p = a;
q = a + strlen(a) - 1;
changeString(p, q);
puts(a);
return 0;
}
void changeString(char * pt1, char * pt2)
{
while (pt1 < pt2)
{
*pt1 ^= *pt2;
*pt2 ^= *pt1;
*pt1 ^= *pt2;
pt1++;
pt2--;
}
}
char *s_gets(char str[], int size)
{
char * y;
int i = 0;
y = fgets(str, size, stdin);
if (y)
{
while (!(str[i] == '\n' || str[i] == '\0'))
i++;
if (str[i] == '\n')
str[i] = '\0';
else
while (getchar() != '\n')
continue;
}
return y;
}
[9]二维数组
-
格式
-
数组成员的访问 a[0][0] a[0][1]
-
数组赋初值
int b[2][3] = {{1,2,3},{4,5,6}}; int b[][3] = {{1,2,3},{4,5,6}}; //可以省略行,但是不能省略列 //int b[2][] = {{1,2,3},{4,5,6}}; 错误的 int b[2][3] = {{1,},{4,}}; //其他的值自动赋值为0 int b[2][3] = {1,2,3,4,5,6}; //体现了二维数组在内存中存储是连续的 -
地址相关内容
运用升维,降维方法,很容易得到 +1 移动的结果 int b[2][3]; b :二维数组的名字,二维数组的首地址,它是常量 b[0]:第0行的首地址 &b :取二维数组的首地址 &b[0][0]:第0个元素的地址 b+1 :移动的是12个字节,正好移动了一行 b[0]+1:移动的是4个字节,正好移动了一个元素 &b+1 :移动的是24个字节,正好移动了整个二维数组的大小 &b[0][0]+1:移动的是4个字节,正好移动了一个元素 b[i][j] <==> *(a[i] + j) <==> *(*(a + i) + j)int b[2][3][4] = {{{{1}, {2}, {3}, {4}}, {{5}, {6}, {7}, {8}}, {{9}, {10}, {11}, {12}}}, {{{13}, {14}, {15}, {16}}, {{17}, {18}, {19}, {20}}, {{21}, {22}, {23}, {24}}}} b :三维数组的名字 b[0]:第1个二维数组 int b2[3][4] &b :取三维数组的首地址 &b[0][0]:第1个二维数组里面的第1个数组的首地址 b[0][0][0]:第1个二维数组里面的第1个数组里面的第一个元素 &b[0][0][0]:第1个二维数组里面的第1个数组里面的第一个元素的首地址 b+1 :移动的是48个字节 b[0]+1:移动的是16个字节 b[0][0]+1:移动的是4个字节 &b+1 :移动的是96个字节,正好移动了整个三维数组的大小 &b[0]+1:同b+1,移动的是48个字节 &b[0][0]+1:同b[0]+1,移动的是16个字节 // 测试代码 #include <stdio.h> int main(int argc, const char *argv[]) { int b[2][3][4] = {{{1, 2, 3, 4}, {5, 6, 7, 8}, {9, 10, 11, 12}}, {{13, 14, 15, 16}, {17, 18, 19, 20}, {21, 22, 23, 24}}}; printf("b = %p\n", b); // b = 0x7ffdbc32b670 printf("b[0] = %p\n", b[0]); // b[0] = 0x7ffdbc32b670 printf("&b = %p\n", &b); // &b = 0x7ffdbc32b670 printf("&b[0][0] = %p\n", &b[0][0]); // &b[0][0] = 0x7ffdbc32b670 printf("*b[0][0] = %d\n", *b[0][0]); // *b[0][0] = 1 printf("&b[0][0][0] = %p\n", &b[0][0][0]); // &b[0][0][0] = 0x7ffdbc32b670 printf("b[0][0][0] = %d\n", b[0][0][0]); // b[0][0][0] = 1 printf("b + 1 = %p\n", b + 1); // b + 1 = 0x7ffdbc32b6a0 printf("b[0] + 1 = %p\n", b[0] + 1); // b[0] + 1 = 0x7ffdbc32b680 printf("b[0][0] + 1 = %p\n", b[0][0] + 1); // b[0][0] + 1 = 0x7ffdbc32b674 printf("&b + 1 = %p\n", &b + 1); // &b + 1 = 0x7ffdbc32b6d0 printf("&b[0] + 1 = %p\n", &b[0] + 1); // &b[0] + 1 = 0x7ffdbc32b6a0 printf("&b[0][0][0] + 1 = %p\n", &b[0][0][0] + 1); // &b[0][0][0] + 1 = 0x7ffdbc32b674 return 0; } -
案例
//访问二维数组元素的四种方法 #include <stdio.h> int main(void) { int i, j; int b[2][3] = {{1, 2, 3}, {4, 5, 6}}; for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) printf("b[%d][%d] = %d\n", i, j, b[i][j]); puts("----------------------------------"); for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) printf("b[%d][%d] = %d\n", i, j, *(*(b + i) + j)); puts("----------------------------------"); for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) printf("b[%d][%d] = %d\n", i, j, *(b[i] + j)); //每次移动4个字节 puts("----------------------------------"); for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) printf("b[%d][%d] = %d\n", i, j, *(&b[0][0] + 3 * i + j)); //每移动一个元素便移动4个字节,每移动一行就移动12个字节,但是本身每次都移动4字节,所以只需要乘i return 0; } -
帮助理解
-
int a; int * p = a; p+1; //表示移动到下一个int类型的数,刚好移动4个字节 //同理 int t[2]; int ** ptr = &t; ptr++; //此时ptr++当然也是移动到下一个t类型的大小的字节,t 是int [2]类型,所以刚好移动8个字节,移动到下一个数组 -
int b[2][3] 其实等同于 (int [3]) b[2] //b是一个含两个元素的数组,里面每个元素都是int 类型的3个数
-
-
作业:让用户输入一个字符串,比如“this is a book”,以单词为单元将其逆置,变为"book a is this" ,只允许定义
char a[100];这一个数组,不允许开辟其他数组(用指针实现)// 思路,反转两次,第一次 koob a si siht,第二次,按照空格和结束符为分隔符来翻转 #include <stdio.h> #include <string.h> #define SIZE 100 void swap1(char *, char *); void swap2(char *); int main(void) { char * p, *q; char str[SIZE] = {0}; puts("Please enter a string."); scanf("%[^\n]", str); p = str; q = str + strlen(str) - 1; swap1(p, q); swap2(str); puts(str); return 0; } void swap2(char * str) { char * p, *q, *temp; p = q = str; puts(str); do { //多空格的话swap中的while中的条件无法满足,所以不必考虑 if (*q == ' ' || *q == '\0') { temp = q - 1; swap1(p, temp); p = q + 1; } }while (*q++); } void swap1(char * p, char * q) { char temp; while (p < q) { temp = *p; *p = *q; *q = temp; p++; q--; } }
day7
[1]一级指针和二维数组
-
//用一维指针来访问二维数组 #include <stdio.h> int main(void) { int ar[2][3] = {1, 2, 3, 2, 3, 4}; int *p; p = ar[0]; for (int i = 0; i < 6; i++) printf("%d ", *p++); //二维数组在内存中也是连续的存储的,所以可以这样 putchar('\n'); puts("-----------------------第二种"); p = &ar[0][0]; for (int i = 0; i < 6; i++) printf("%d ", p[i]); //p[i] <==> *(p + i) putchar('\n'); puts("-----------------------第三种"); for (int i = 0; i < 2; i++) { p = ar[i]; for (int j = 0; j < 3; j++) printf("%d ", p[j]); } putchar('\n'); return 0; }
[2] 指针数组
-
定义格式:int * a[3],他只是个一维的
-
因为[ ]的优先级比*优先级高,所以a先是一个数组,数组里面的类型是指向 int 类型的指针
-
int a = 5, b = 6, c = 7; int *p[3] = {&a, &b, &c} // + 1 移动的问题 p :数组的首地址 p[0] :数组中成员的地址 *p[0] p + 1 :移动了一个int * ,即四个字节 p[0] + 1:相当于&a + 1,其实就是移动了一个int类型,也是4个字节 *p[0] + 1:值加一
-
-
实例
-
#include <stdio.h> int main(void) { int a = 1, b = 3, c = 5; int * p[3]; p[0] = &a; p[1] = &b; p[2] = &c; for (int i = 0; i < 3; i++) { printf("p[%d] = %d\n", i, *p[i]); } return 0; } -
命令行参数:
-
argc为参数个数
-
argv为他输入命令的字符串数组
-
练习:在命令行中输入3个三位数,把这3个数的十位分离出来重新按顺序组合成为一个新的三位数
#include <stdio.h> #include <math.h> int main(int argc, const char * argv[]) { const char * p; int num1 = 0, num2 = 0, num3 = 0; puts("------第四种------"); //不需要使用pow函数 for (int i = 1; i < argc; i++) num4 = num4 * 10 + (*(*(argv + i) + 1) - '0'); printf("num4 = %d\n", num4); puts("方法一") for (int i = argc - 1, k = 0; i > 0; i--, k++) { p = argv[i]; num1 += pow(10, k) * (*(p + 1) - '0'); } printf("num1 = %d\n", num1); puts("------方法二--------"); for (int i = argc - 1, k = 0; i > 0; i--, k++) { p = argv[i]; num2 += pow(10, k) * (p[1] - '0'); } printf("num2 = %d\n", num2); puts("-------方法三-------"); for (int i = argc - 1, k = 0; i > 0; i--, k++) num3 += pow(10, k) * (argv[i][1] - '0'); printf("num3 = %d\n", num3); return 0; }
-
-
[3] 数组指针
-
定义的格式:
char (*a)[3];a 指向一个内含三个 char 类型值的数组int (*b)[2];b 指向一个内含两个 int 类型的值的数组
-
含义:他是一个指针,这个指针指向的是一个二维数组
#include <stdio.h> int main(void) { int a[2][3] = {1, 2, 3, 4, 5, 6}; int (*pti)[3]; pti = a; printf("a = %p, pti = %p\n", a, pti); printf("a + 1 = %p, pti + 1 = %p\n", a + 1, pti + 1); printf("*a = %p, *pti = %p\n", *a, *pti); printf("*(a + 1) = %p, *(pti + 1) = %p\n", *(a + 1), *(pti + 1)); printf("(*(a + 1) + 1) = %p, (*(pti + 1) + 1) = %p\n", (*(a + 1) + 1), (*(pti + 1) + 1)); //相对于上面 + 4 return 0; } 结果 a = 0x7ffcf15eb050, pti = 0x7ffcf15eb050 a + 1 = 0x7ffcf15eb05c, pti + 1 = 0x7ffcf15eb05c *a = 0x7ffcf15eb050, *pti = 0x7ffcf15eb050 *(a + 1) = 0x7ffcf15eb05c, *(pti + 1) = 0x7ffcf15eb05c (*(a + 1) + 1) = 0x7ffcf15eb060, (*(pti + 1) + 1) = 0x7ffcf15eb060 -
数组指针的应用
#include <stdio.h> int main(void) { int i, j; int ar[2][3] = {1, 2, 3, 4, 5, 6}; int (*pti)[3] = ar; puts("---------第一种----------"); for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) printf("ar[%d][%d] = %d\n", i, j, pti[i][j]); puts("---------第二种----------"); for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) printf("ar[%d][%d] = %d\n", i, j, *(pti[i] + j)); puts("---------第三种----------"); for (i = 0; i < 2; i++) for (j = 0; j < 3; j++) printf("ar[%d][%d] = %d\n", i, j, *(*(pti + i) + j)); return 0; } -
显然数组指针指向一个数组时,这个数组指针就和数组名基本相同,唯一不同的是数组指针能够自加,他是一个变量,而数组名是一个常量,不能做自加
-
练习:int a[5][4], (*p)[4]=a;,数组a的首地址为100,*(p+2)+3等于多少
显然当数组指针指向一个数组时,这个时候数组名和数组指针一样 所以*(p+2)+3 和 *(a+2)+3 就相当于 a[2] + 3,即第3行第4列元素的地址 -
帮助理解
-
int ar[2][3] = {1, 2, 3, 4, 5, 6}; int (*t)[3]; //其实这里的 int (*t)[3] 等同于 (int [3]) *t,所以每次t+1移动的是3个int类型的字节,即12个字节
-
[4] 二级指针
-
定义格式:char **p; int **p
-
含义:指向一级指针的地址就是二级指针
-
实例
-
#include <stdio.h> int main(void) { int a = 10; int *p = &a; int **q = &p; printf("a = %d, *p = %d, **p = %d\n", a, *p, **q); printf("&a = %p, p = %p, &p = %p, *q = %p, q = %p,&q = %p\n", &a, p, &p, *q, q, &q); printf("&a+1 = %p, p+1 = %p, &p+1 = %p, *q+1 = %p, q+1 = %p, &q+1 = %p\n", &a+1, p+1, &p+1, *q+1, q+1, &q+1); // int int int * int int * int ** return 0; } 结果 a = 10, *p = 10, **p = 10 &a = 0x7ffe6c044754, p = 0x7ffe6c044754, &p = 0x7ffe6c044758, *q = 0x7ffe6c044754, q = 0x7ffe6c044758,&q = 0x7ffe6c044760 &a+1 = 0x7ffe6c044758, p+1 = 0x7ffe6c044758, &p+1 = 0x7ffe6c044760, *q+1 = 0x7ffe6c044758, q+1 = 0x7ffe6c044760, &q+1 = 0x7ffe6c044768 -
假如:&a:0xc4 &p:0xd8 &q:0x10
&a + 1 = c8; 加int p + 1 = c8; 加int &p + 1 = f0; 加int * 64位加8 *q + 1 = c8; 加int q + 1 = f0; 加int * &q + 1 = 18 加int ** -
int a = 10; int *p = &a; int **p = &p; *p++; 打印的是10,然后p自加一 *(p++); 和上面一样 *q++; 打印p即a的地址&a,q即p的地址&p自加一 p++; 打印就是&a,然后p自加一 (*q)++; 打印的是a的地址,p指向a的下一个地址 (*p)++; 打印的是10,然后把里面的值自加一,a的值变为11 (**q)++; 打印10,然后把值自加,a的值变为11
-
[5] 指针函数
-
函数返回的是一个指针
char *func(void);int *func(int a, int b);
-
含义:他是一个函数,返回指针
-
错误使用
char *func(void) { char a = 48; / return &a; } //错误的,函数的内存是在调用的时候分配的, //在调用完之后内存就被释放了,此时这个指针就是野指针 //永远不要将局部变量的地址返回。 int main(int argc, const char *argv[]) { printf("%c\n",*func()); return 0; }
[6] 函数指针
-
格式:
char (*func)(void);int (*func)(int a, int *b);
-
含义:它是一个指针,这个指针指向的就是一个函数
-
案例
#include <stdio.h> int add(int, int); int sub(int, int); int main(void) { int (*func)(int, int); //可以省略参数 printf("sum = %d\n", add(100,200)); printf("sub = %d\n", sub(100,200)); func = add; //函数的名字本身就是一个地址 printf("sum = %d\n", func(100,200)); func = ⊂ //这种也可以,但是*符号要和func括起来 printf("sum = %d\n", (*func)(100,200)); //括号里面等同于*(&sub),结果还是sub 这种方法不推荐 return 0; } int add(int a, int b) { return (a + b); } int sub(int a, int b) { return (a - b); } -
函数指针的作用
#include <stdio.h> int add(int, int); int sub(int, int); void show(int, int, int (*func)(int, int)); int main(void) { show(5, 10, add); //可以只用show这个函数把加减乘除的函数都给打印出来 show(5, 10, sub); return 0; } void show(int a, int b, int (*func)(int a, int b)) //回调函数 { //在函数内部调用形参中的函数指针指向的函数,这个形参回调到的函数就叫回调函数 printf("%d\n", func(b, a)); } int add(int a, int b) { return (a + b); } int sub(int a, int b) { return (a - b); }
[7] 函数指针数组
-
格式:
char (*func[2])(int a, int b);int (*func[3])(double a);
-
含义:他是一个数组,里面装的是指针,指针指向的是函数
-
实例
#include <stdio.h> int add(int, int); int sub(int, int); int main(void) { //int (*func[2])(int, int) = {add, sub}; //这样初始化,或者如下分别初始化也行。 int (*func[2])(int, int); func[0] = add; //第一个指针指向add函数 func[1] = sub; //第二个指针指向sub函数 printf("sum = %d\n", func[0](5, 10));//也可以写成(*func)(5, 10) printf("sub = %d\n", func[1](5, 10));//同理,(*(func + 1))(5, 10) return 0; } int add(int a, int b) { return (a + b); } int sub(int a, int b) { return (a - b); }
作业:求p[0]--p[5]的值(这个才有点点挑战)
#include <stdio.h>
int main(void)
{
char* data = "12345678";
short* tmp = NULL;
char p[6] = {0};
tmp = (short *)&p[2];
*tmp = atoi(&data[4]);
//p[0] = ?, p[1] = ?, p[2] = ?,
// p[3] = ?, p[4] = ?, p[5] = ?。
return 0;
}
答案
0 0 0x2e 0x16 0 0
首先,data 是 char * 类型的,而tmp 是 short * 类型的,虽然都是指针类型的,占4个字节,但是short * tmp 每次访问2个字节的空间。所以
atoi(&data[4])的十进制是5678,16进制是162e,*tmp = atoi(&data[4])就等于162e,然后tmp指向的p[2],又因为他访问的是2个字节的空间,所以他会把2e16放到[2]p[3]中,至于16 2e如何放就要考虑大小端的问题
小端:数据的低位存放在低地址中,数据高位存放在高地址中
大端:数据的低位存放在高地址中,数据高位存放在低地址中
一般我们的系统是小端,但是在网络方面,都是用大端
所以162e中的低位2e就给低地址p[2]中,高位16就给高地址p[3]中,如果是大端就刚好相反
所以结果为0 0 0x2e 0x16 0 0,其他的没变
注:如何判断大小端
int a = 0x12345678;
char *b = &a;
if (*b == 0x78) //如果把低位中的0x78赋给了b,就是小端
puts("small");
else
puts("big"); //如果把低位中的0x12赋给了a,就是大端
return 0;
day8
[1] typedef的用法
-
题目
用变量a给出下面的定义 a) 一个整型数 int a; b) 一个指向整型数的指针 int *a; c) 一个指向指针的的指针,它指向的指针是指向一个整型数 int **a; d) 一个有10个整型数的数组 int a[10]; e) 一个有10个指针的数组,该指针是指向一个整型数的 int *a[10]; f) 一个指向有10个整型数数组的指针 int (*a)[10]; g) 一个指向返回值是整型参数是整型的函数指针 int (*a)(int ); h) 一个有10个成员的数组,数组中返回值整型参数是整型的函数指针 int (*a[10])(int); -
typedef:类型的重定义,把上述的变量名去掉就是类型,有如下类型
int ;int *;int **;int [10];int * [10];int (*) [10];int (*)(int );int (*[10])(int);
-
定义:把上面变量名全部替换成你要定义的新的类型
typedef int INT;typedef int *INT_P;typedef int **INT_PP;typedef int INT_A[10];typedef int *INT_P_A[10];typedef int (*INT_A_P)[10];typedef int (*FUNC_P)(int);typedef int (*FUNC_P_A[10])(int)
-
以上运用的实例
#include <stdio.h> typedef int INT; typedef int *INT_P; typedef int **INT_PP; typedef int INT_A[10]; typedef int * INT_P_A[10]; typedef int (*INT_A_P)[10]; typedef void (*VOID_F_P)(void); typedef void (*VOID_F_P_A[2])(void); void hello(void); void world(void); int main(void) { puts("---------------------"); INT a = 0, b = 2; printf("a = %d, b = %d\n", a, b); puts("---------------------"); INT_P p = &a, q = &b; printf("p = %p, q = %p\n", p, q); puts("---------------------"); INT_PP pt = &p, qt = &q; printf("pt = %p, qt = %p\n", pt, qt); puts("---------------------"); INT_A ar = {1, 2, 3, 4, 5, 6, 7, 8, 9, 0}; for (int i = 0; i < 10; i++) printf("%d ", ar[i]); putchar('\n'); puts("---------------------"); INT_P_A arr; arr[0] = &a; arr[1] = &b; printf("arr[0] = %p, arr[1] = %p\n", arr[0], arr[1]); puts("---------------------"); int arra[2][10] = {{1, 2, 3, 4, 5, 6}, {7, 8, 9, 0, 10, 11}}; INT_A_P pti = arra; for (int i = 0; i < 2; i++) for (int j = 0; j < 10; j++) printf("%d ", pti[i][j]); putchar('\n'); puts("---------------------"); VOID_F_P myfunc = hello; myfunc(); puts("---------------------"); VOID_F_P_A myf_p_a; myf_p_a[0] = myfunc; myf_p_a[1] = world; myf_p_a[0](); myf_p_a[1](); return 0; } void world(void) { puts("world"); } void hello(void) { puts("Hello."); } -
define 和 typedef 区别
-
define 是预处理指令,在预处理阶段只是完成简单的字符串替换。typedef 是类型重定义,实在编译的时候完成的类型替换
-
#define INT_D int * typedef int *INT_T; int main(void) { INT_D a, b; //此时他只是简单的替换,所以实质是 int * a, b; //明显此时的a是一个int *类型的,但是b只是一个int类型的 INT_T a, b; //此时a, b都是int *类型的 }
-
-
define前面有#号(所有的预处理指令前面都有#号,例如#if 0 #endif),typedef不需要
-
define 结尾不需要分号,但是typedef 后面需要有分号结尾
-
define 在定义的时候新定义的名字在前面,typedef 在后面
-
-
gcc的4个步骤
-
预处理:头文件的展开,宏的替换,注释的删除,不会对语法检查
gcc -E test.c -o test.i
-
编译:将预处理后的文件编译生成.s的汇编文件,会对语法检查
gcc -S test.i -o test.s
-
汇编:将汇编文件生成二进制的目标文件,不回去链接库文件
-
gcc -test.s -o test.o -
拓展
库文件,例如你的.c文件里面用了开平方函数sqrt,此时你需要在编译的时候使用-lm -l就是链接库的意思,-lm是链接m这个库,m的库是libm.so(动态库) 在ubuntu中 动态库:libxx.so:动态库:编译的时候只是去链接了这个库, 执行的时候才会回来调用库中的函数 静态库:libxx.a:静态库:编译的时候直接将库中的函数编译生成的汇编文件 放到a.out中(占内存,但是速率效率高) 在windows中 xxx.dll: 库文件中不能有main函数的
-
-
链接:链接库文件,生成一个elf文件。
- elf,中的 e 是 exec 可执行的意思,l 是小端,f 是file 的意思,小端可执行文件
gcc test.o -o test- ldd命令,查看文件包涵了什么库的文件,
ldd a.out
-
[2] linux内核内存的划分
-
实际的物理内存不管多大,都通过 mmu(内存映射单元) 来映射到一个 4G 的虚拟内存上,这么做的目的是为了兼容不同大小的物理内存的
-
虚拟内存和物理内存的关系
- 虚拟地址并不真实存在,他是通过 mmu(内存映射单元) 对物理内存和虚拟内存做了一个关联,程序中变量存放的时候,实在物理内存中存放着的,但是我们使用%p打印的时候显示的是虚拟地址。
-
内存的划分
- 【0 - 3G】是用户空间的内存,每一个进程所单独拥有自己的【0 - 3G】内存空间,所以【0 - 3G】的内存是有多份的
- 【3 - 4G】是内核空间(运行的是操作系统),内核空间只有一份
- 注:进程,程序的一次执行的过程
|-----------------------------------------| 4G | 内核空间(运行的是操作系统) | |-----------------------------------------| 3G | 栈区 | |*****************************************| | 堆区 | |*****************************************| | 静 | .data段 | | 态 |**********************************| | 区 | .bss段 | | |*********************************| | | .text段 | | |**********************************| | | .ro段 | |------------------------------------------| 0G -
各个分区存放的数据
-
栈区:函数的执行就是在栈空间完成,局部变量、函数的形参也是在栈空间分配的,栈空间定义的变量如果不赋初值,它的值是随机值。每次函数执行完就会被释放
-
堆区:在程序中使用malloc函数分配的内存都是在堆区
-
静态区
-
.text:这个区间存放的是可执行程序。他又叫代码段。 -
.data:在函数内部使用 static 修饰的已初始化的变量,全局的已初始化的变量或者全局被 static 修饰的已初始化的变量都在 .data 段 -
.bss:static修饰的但是未初始化的变量,全局的未初始化的变量都在 .bss,.bss段中的变量初始值都是0. -
.ro只读的数据段,也叫字段常量区,这个段中的内容不允许修改。char * p = "helloworld",该指针指向的就是常量区"helloworld",也就是 .ro段,他只能读,不能写 -
案例:
#include <stdio.h> int a; //.bss段存放,初值是0 int b = 10;//.data,初值是10 static int c;//.bss段,初值是0 static int d = 20;//.data,初始值20 static int add(int a,int b) { return (a+b);//栈 } void func(void) { static int a = 1; //.data段 printf("a = %d\n",a++); } int main(int argc, const char *argv[]) { int a; //在栈区存放,它是随机值 int b = 10;//在栈区存放,它的值是10 static int c;//在.bss段,它的值是0 static int d = 20;//在.data段,它的值是20 char buf[] = "hello"; //buf在栈区,他是把"hello"复制到栈区,然后在栈区内改 char *p = "hello"; //p指向的是常量区 add(5,10); //栈 func(); //栈 func(); //栈 return 0; }
-
-
[3] 存储类型(6个)
-
static:
static int a- 延长变量的生命周期
- 限定作用域(他修饰的变量后者函数只能在本文件中使用)
-
auto:修饰的是自动类型的变量
- 局部变量前默认就有这个auto修饰
- 非自动类型的变量有:static 修饰的变量,全局变量
-
register:寄存器类型的变量
- register int a = 10;
- 寄存器类型的变量执行效率高,但是不能对变量取地址
-
extern:声明函数或者变量没有在本文件中实现,调用的是其他文件中的函数或者变量
-
const:
-
const修饰的是一个只读的变量
-
const 在谁前面,谁就不允许修改
const int *p; //*p 不允许修改,注意这里说的是不能通过*p来修改值,但是通过其他方法还是可以修改值的,比如const int *p = &a,可以通过a来修改a的值(a = 12),但是不能通过*p修改该值 int const *p; //*p 不允许修改 int * const p; //p 不允许修改,即p不允许指向别的地方 const int *const p; //*p 和 p都不能修改 const int arr[10]; // arr的值不可修改 -
在main函数中的 const int * argv[],中的const修饰的是 * argv[], 而 argv[0] = "./a.out"; argv[1] = "123" //假如你输入的是:./a.out 123 456 777 argv[1] = "456" argv[1] = "777" 那么 *argv[0] = '.'; *argv[1] = '1'; *argv[2] = '4'; *argv[7] = '7'; 所以他指的是每个字符串的第一个字符不能修改,但是实践上,其他的字符(比如'2','3','4','6'等)都不能修改,因为实践上系统会把你输入的命令行参数都放在字符串常量区里面的,里面的都是只读类型 -
const 修饰的全局变量在 .ro段
-
const 修饰的局部变量在栈上。
-
-
volatile:易变的,不稳定的
- volatile 修饰的变量每次都从内存中取最新的值,不是从高速缓存cache中取值,volatile修饰的变量编译器不会对他优化
[4] 动态内存分配(malloc)
-
动态内存:在程序执行的时候根据需求去分配对应的内存大小
-
他在 stdlib.h 头文件中,格式
void *malloc(size_t size); -
功能:分配内存 (0-3G:堆区)
-
参数:size_t size的单位是字节
-
返回值:分配的内存的首地址
-
void free(void *ptr)- 功能:释放内存
- 参数:ptr 是你想要释放的内存的首地址,
- 注:在free 完之后,一定要记得将指针赋值为NULL,如果不赋值为NULL,当再次使用这个指针时,这个指针就是野指针
- 注意:在分配得到的内存首地址,不能做自加或者自减的操作,如果你这么做了,在调用free 函数的时候,就会出像错误
-
案例
#include <stdio.h> #include <stdlib.h> int main(int argc, const char *argv[]) { int num,i; int *p = NULL; if(argc < 2) { printf("input error\n"); return -1; //返回0表示正常结束,放回-1表示异常结束 } num = atoi(argv[1]); p = (int *)malloc(num*sizeof(int)); for(i=0; i<num; i++){ *(p+i) = i; //p[i] = i; printf("*p = %d\n",*(p+i)); } free(p); p = NULL; //必须要让它指向空指针,这样对空指针做操作的话会报错 *p = 50; //这个时候就会报错, printf("*p = %d\n",*p); return 0; } -
野指针:指针指向的内存你没有申请,这种指针就是野指针
-
内存泄漏:比如在一个服务器上一直使用malloc申请内存,但是没有主动释放内存,导致内存越用越少,这种想象就叫做内存泄漏
[5] 结构体
-
格式
struct AA{ char a; int b; int c[10]; int *t; }; //后面有分号 -
含义:结构体是一个构造类型,它是不同种数据类型的集合。他在内存上也是连续的。结构体使用 struct 来标识。
-
案例,(用点来访问)
#include <stdio.h> #include <string.h> int main(void) { struct student{ char name[20]; char sex; int age; float high; float score; }; struct student p1; //用变量名p1后面加点来标识 strcpy(p1.name,"zhangsan"); p1.sex = 'm'; p1.age = 50; p1.high = 2.46; p1.score = 25; puts("-------------------"); printf("name = %s\n", p1.name); printf("sex = %c\n", p1.sex); printf("age = %d\n", p1.age); printf("high = %.2f\n", p1.high); printf("score = %.2f\n", p1.score); return 0; } -
案例,把上述代码变成2个学生,然后循环输入输出,(用结构体指针变量来访问)
#include <stdio.h> #include <string.h> #include <stdlib.h> int getNum(void); int main(void) { int num = 0; struct student{ char name[20]; char sex; int age; float high; float score; }; struct student *p = NULL; puts("Please enter student number."); num = getNum(); printf("num = %d\n", num); p = (struct student *) malloc(sizeof(struct student) * num); if (p == NULL) { puts("alloc memory error."); return -1; } for (int i = 0; i < num; i++) { printf("Please enter the name of the %d student number\n", i + 1); scanf("%*c%[^\n]", (p+i)->name);//这里就可把第一次的换行符跳过 printf("Please enter the sex of the %d student number\n", i + 1); scanf("%*c%c", &(p+i)->sex); printf("Please enter the age of the %d student number\n", i + 1); scanf("%d", &(p+i)->age); printf("Please enter the high of the %d student number\n", i + 1); scanf("%f", &(p+i)->high); printf("Please enter the score of the %d student number\n", i + 1); scanf("%f", &(p+i)->score); } for (int i = 0; i < num; i++) { puts("------------------------------------------------------"); printf("name = %s\n", (p+i)->name); printf("sex = %c\n", (p+i)->sex); printf("age = %d\n", (p+i)->age); printf("high = %.2f\n", (p+i)->high); printf("score = %.2f\n", (p+i)->score); puts("------------------------------------------------------"); putchar('\n'); } free(p); p = NULL; return 0; } int getNum(void) { int status, output; while (!((status = scanf("%d", &output)) == 1 && output > 0)) { if (status != 1) scanf("%*s"); puts("enter error, Please enter a integer greater than 0"); } scanf("%*s"); //相比于while循环,多了一个换行符,即换行符还在缓冲区 return output; } -
结构体的使用
-
用点来访问
struct student{ char name[20]; char sex; 'w' 'm' int age; float high; float score; }p1, p2, *p3; //定义p1的变量,p2的变量,p3的指针变量,和int p1, p2, *p3类似 strcpy(p1.name,"zhangsan"); p1.sex = 'm'; p1.age = 50; p1.high = 2.46; p1.score -
用 '->'指针来访问
struct student *p1 = NULL; p1 = (struct student *)malloc(sizeof(*p1)); strcpy(p1->name,"zhangsan"); p1->sex = 'm'; p1->age = 50; p1->high = 2.46; p1->score = 25; free(p1); p1 = NULL; -
用法3,不可以可以跳过赋值
struct student{ char name[20]; char sex; int age; float high; float score; }p1 = {"zhangsan",'m',50,2.46,25.5}; //后面有分号,定义p1的时候赋初值 //在最后赋值,但是这种赋值不能跳过赋值,按顺序赋值,可以省略后面的赋值 //例如{"zhangsan",'m',50} 这种可以,直接不赋值给后面的,后面的自动补0, //{"zhangsan",2.46,25.5} 这种不行,跳过了性别和年龄直接给后面的身高赋值 -
用法4,可以跳过赋值
struct student{ char name[20]; char sex; int age; float high; float score; }; struct student p1 = { .score = 25.5, //可以不按顺序赋值 .name = "zhangsan", //注意逗号结尾 .sex = 'm', }; //定义p1的时候赋初值 //使用.的方式可以跳过成员赋值 -
用法5,和typedef结合使用
typedef struct student{ char name[20]; char sex; 'w' 'm' int age; float high; float score; }p1; //p1就是类型了 p1 p; //在这里p1就相当于 struct student -
用法6,无名结构体
typedef struct { // -->无名结构体,此时不能像上面那样用struct student p来赋值了 char name[20]; char sex; 'w' 'm' int age; float high; float score; }p1,p2; //p1,p2都是类型了,想要赋多个值,在后面加逗号和变量名 p1 p;
-
[6] 结构体数组
-
可以这样定义
struct student{ char name[20]; char sex; 'w' 'm' int age; float high; float score; char *t; //结构体里面可以定义指针 char (*func)(int); //也可以定义函数指针,但是不能定义函数,因为函数不能确定大小,只有在执行时候才能知道大小,结构体存放的成员大小一定要是确定的 }; struct student st[2]; st[0].name st[0].sex st[0].age st[0].high st[0].score st[1].name st[1].sex st[1].age st[1].high -
也可以这样定义
struct student { char name[20]; char sex; 'w' 'm' int age; float high; float score; }st[2] = { {"zhangsan",'m',50,2.46,25.5}, {"lisi",'m',50,2.46,25.5} }; -
同理也可以这样
struct student{ char name[20]; char sex; 'w' 'm' int age; float high; float score; }st[3] = { [0] = {"zhangsan",'m',50,2.46,25.5}, [2] = {"lisi",'m',50,2.46,25.5} //这样可以跳成员来赋值 }; -
也可以这样
struct student{ char name[20]; char sex; 'w' 'm' int age; float high; float score; }st[3] = { [0] = {.name = "zhangsan", .sex = 'm', .score = 60.23}, [2] = {"lisi",'m',50,2.46,25.5} //这样可以跳数组成员来赋值 };//同理也可以跳数组里面的成员赋值
[7] 结构体内存对齐
- 如果结构体中的成员小于4个字节,最大类型是什么就按照这个类型的字节数对齐,如果结构体中的成员大于等于4个字节,那就按照4字节对齐
- 注意:最大4字节只是对于32位操作系统来说的,64位操作系统最大对齐为8字节
-
//以下实例是按32位操作系统来说的 struct aa{ //1个字节对齐 char a; char b; char c; } //每个变量一个字节,一共3个字节 struct aa{ //按2个字节对齐 char a; //他占两个字节 short b; short c; } //一共占6个字节 struct aa{ //按4个字节对齐,最大就是四个字节 char a; //奇数位不能存short类型 short b; //short 和 char两个在一起一共占4个字节 int c; //占4个字节 } //一共是8个字节 struct aa{ //按4个字节 char a; //先占一个字节,剩下3个字节,存不下int类型的,所以自动对齐占4字节 int b; //占4字节 short c; //自动对齐占4个字节 }//一共占12个字节 struct aa{ //还是按4字节对齐,最大对齐就是4字节 //64位系统中。按8字节对齐 long long a; //占8个字节 //64位系统中,8 char c; // // short e; //和char 一起占4字节 // int b; //占4个字节 //64位系统中,和上面的char、short一起占8个字节 float *t; //4个字节 //64位系统中,占4个字节,但是要对齐所以占8个字节 } //一共20个字节 //64位系统中,占24个字节
[8] 共用体
-
格式
union aa{ char a; short b; int c; } union aa A; A.c = 0x12345678; printf("a = %#x, b = %#x\n", A.a, A.b); //如果是小端字节序,那么A.a等于0x78,结合下图来看,A.b = 0x5678
-
设计的目的,可供选择,加入我想把上面的student结构体改成描述老师和学生的,那么老师就没有分数这一选项,可以有工资,但是学生就没有工资,只用分数
-
访问方式和struct一样
struct person{ char name[20]; char sex; 'w' 'm' int age; float high; union s{ float score; int money; }a; }; struct person stu = { .name = "zhangsan" .a.score = 50; }; struct person teh = { .name = "lisi" .a.money = 50000; };
[9] 枚举
-
格式
enum AA{ a, b, c, d, e, f, } //默认的话a = 0, b = 1, c = 2…… //但是可以自己赋值,比如 enum AA{ a = 1, b, c = 8, d, e, f = 100, } //这时候a = 1, b = 2, c = 8, d = 9, e = 10, f = 100 -
含义:他是有限数据的罗列,默认的第一个值是0,下面每一个成员都是上面那个值加一
-
他占的大小不管在64位还是32位中都是4字节
-
直接用,不需要像结构体那样去访问
#include <stdio.h> int main(void) { enum AA{ A, B, C = 100, D, E, F, }; enum AA a; //如果非要用变量来访问就用这种方法 a = C; printf("a = %d, %d\n", a, C); printf("D = %d, F = %d\n", D, F); printf("size = %zd\n", sizeof(enum AA)); return 0; }

浙公网安备 33010602011771号