文章目录
-
-
- 1.typedef:类型重命名
- 2.请问p和q的类型
- 3.关键字sizeof
- 4.进制数转换的贪心算法
- 5.c/c++的常变量不同侧重点
- 6.' ' 和" "
- 7.ascii码值
- 8.转义字符
- 9.关于0 '0' '\0'
- 10.关于\000 和\xff
- 11.字符串与\0
- 12.宏和字符串
- 13.char ch = 'abcd'问题
- 14.作用域(可见性)和生存期
- 15.C语言运算符优先级
- 16.小端存储
- 17.标准输入文件0、标准输出文件1、标准错误输出文件2
- 18.宏和typedef
- 19.extern关键字
- 20.static关键字的使用
- 21.4G的虚拟空间
- 22.数据在内存中存放的位置
- 23.const修饰定义的变量和#define宏替换的区别(见5)
- 24.浅谈宏函数
- 25.字符串和字符数组
- 26.指针移动和*解引用
- 27.指针为什么要有类型
- 28.为什么需要二级指针
- 29.指针数组和数组指针
- 30.const和指针变量的关系
- 31.函数指针
-
1.typedef:类型重命名
一切合法的变量名的定义(普通变量、指针变量、数组变量、函数指针、结构体)都可以用typedef转换成类型名
加typedef之前
unsigned int UINT; //普通变量
int* PINT; //指针变量
int Array[10]; //数组变量
void (*pfun)(); //函数指针
struct Student stu; //定义结构体变量stu
struct Student *pstu;//定义结构体指针pstu
加上typedef之后
typedef unsigned int UINT; //UINT类型名
typedef int* PINT; //PINT指针类型名
typedef int Array[10]; //Array数组类型名
typedef void (*pfun)(); //函数指针类型名
typedef struct Student stu; //stu类型
//使用stu s1;
typedef struct Student *pstu; //结构体指针类型
//使用:pstu p1 = NULL;
(1)给已有的类型名起别名
typedef unsigned char u_int8;
typedef unsigned short u_int16;
typedef unsigned int u_int32;
typedef unsigned double u_int64;
(2)对已有的声明,变量名的定义加上typedef 变成类型名
#include <stdio.h>
typedef int Arr[10];
int main()
{
Arr a = {
1, 2, 3, 4, 5 };
for (int i = 0; i < 5; i++)
{
printf("%4d ", a[i]);
}
printf("\n");
}
结果:
结构体经典写法:
#include <stdio.h>
struct Student
{
...;
}stu, *pstu;
//这样是定义了stu结构体变量和pstu结构体变量指针
//前面加上typedef后
typedef struct Student
{
...;
}stu, *pstu;
//stu便成了自定义的结构体类型
//pstu变成了自定义的结构体指针的类型
//从而可以使用该类型进行定义变量
2.请问p和q的类型
int* p, q;
结果:
*和变量名结合,不是与类型名结合,所以p是int指针类型,q是int类型;
结合1,想同时定义p和q两个指针:
#include <stdio.h>
typedef int *PTR;
int main()
{
PTR p,q;
return 0;
}
结果:
3.关键字sizeof
- sizeof是一个关键字,在编译期间确定类型和大小;
#include <stdio.h>
int main()
{
int a = 0;
int x;
//在编译期确定
x = sizeof(++a);
//等价于x = 4;
printf("a = %d\n", a);
return 0;
}
//结果a = 0
- sizeof和strlen()的区别
-
调用时机不同:sizeof是关键字,编译期间确定类型和大小
strlen()是函数,在运行期间调用函数
-
功能不同:strlen()是专门计算字符串的长度;
sizeof在计算字符串所占用的空间大小;
char buff[] = {
"helloworld"};
int len = strlen(buff); //len = 10;
int size = sizeof(buff); // size = 11;
4.进制数转换的贪心算法
https://blog.csdn.net/xiaoxiaoguailou/article/details/120920622
5.c/c++的常变量不同侧重点
vs2019的全局变量未初始化默认为0,局部变量未初始化是随机值,使用该值编译不通过
-
c中的常变量侧重与"变量",不能使用常变量定义数组,编译期不通过;
-
c++中的常变量侧重于"常",可以使用该常变量定义数组;
-
C++常变量类似于宏,却有不同与宏
- 编译时期不同
- 与宏有所不同:宏在预编译时进行宏替换;使用常变量定义数组是在编译期进行确定的;
- 是否存在类型和占用空间
- 宏不存在类型,不占用空间
- 常变量有类型,占用空间
- 安全性
- 宏不存在类型,没有类型检查,不安全
- 常变量有数据类型,有类型检查,比较安全
- 编译时期不同
6.’ ’ 和" "
’ ‘是字符的定界符, 在’前面加上\后转义变成单引号字符–》\’
例如:
char ch = '''; //error ''是定界符,想使用单引号字符需要转义
char ch = '\''; //true
""是字符串的定界符
7.ascii码值
8.转义字符
9.关于0 ‘0’ ‘\0’
int main()
{
char cha = 0; //ASCII值为0对应的字符就是空字符
char chb = '0'; //字符0对应的ASCII值48
char chc = '\0'; //等于cha == 》空字符
char chd = ' '; //空格字符ascii值是32
return 0;
}
10.关于\000 和\xff
\000将八进制数000转换成十进制对应的ascii码值,码值对应的字符;
- 其中八进制数有效范围000~377,因为char一字节最大取值255,其对应的八进制数就是377;八进制数超出该范围编译器就会报错
char str[] = {
"pzj\141hello"};
//八进制的141转换成十进制的97 //pzjahello
//如果是"pzj\1411hello" //pzja1hello
//如果是"pzj\148hello" //只会转义\14因为8超出0~7
//如果是"pzj\889hello" //此时的\就会被省略
int len = strlen(str); //len = 9
\x00将十六进制的00转换成十进制对应的ASCII码值,码值对应的字符;
-
其中十六进制数的有效范围是0~ff,因为char最大255,对应的十六进制数就是ff;十六进制数超出该范围也会报错
char str[] = { "hello\61xworld"}; //helloaworld
11.字符串与\0
字符串的printf("%s")打印、strcpy拷贝、strcat连接、等函数都是以字符串的\0作为结束条件
char str[] = {
"hello\0world"};
int size = sizeof(str); //size = 12
int len = strlen(str); //len = 5
printf("%s\n", str); //hello
12.宏和字符串
#define MAX 1000
int main()
{
char str[] = {
"helloMAX"};
printf("%s\n", str); //helloMAX
}
- 原因:MAX是字符串的一部分,不是标识符,不会被宏替换
13.char ch = 'abcd’问题
C++有一个叫做“多字符文字”的东西。'1234'
就是一个例子。他们有类型int
,它是实现–定义了它们所具有的值以及它们可以包含多少字符。
那算不了什么直接与字符被表示为整数的事实有关,但在实现中,很有可能'1234'
定义为:
'1' + 256 * '2' + 256 * 256 * '3' + 256 * 256 * 256 * '4'
或:
'4' + 256 * '3' + 256 * 256 * '2' + 256 * 256 * 256 * '1'
14.作用域(可见性)和生存期
- 作用域:针对的是编译和链接的过程
- 函数、全局变量从定义起(整个文件可见)全局可见,没有局部函数一说
- 生存期(生命期):针对的是程序的执行过程
- 局部变量的生存周期:函数被调用开始,函数执行结束时消亡,释放存储空间。存储在.stack区
- 全局变量的生存期:从程序开始运行时开始,到程序执行结束时结束。存储在.data区
- 动态生命期(堆区空间):标识符由特定的函数调用或运算来创建和释放,如果调用malloc()为变量分配存储空间开始,free()释放存储空间结束。存储在堆区.heap
编译错误:g_value未定的标识符
#include <stdio.h>
void Test()
{
int a = g_value;
}
int g_value = 10;
int main()
{
Test();
return 0;
}
错误理解:误认为程序一边编译一边运行,g_value存在于.data段
15.C语言运算符优先级
优先级 | 运算符 | 名称或含义 | 使用形式 | 结合方向 | 说明 |
---|---|---|---|---|---|
1 | [] | 数组下标 | 数组名[常量表达式] | 左到右 | |
() | 圆括号 | (表达式) 函数名(形参表) | |||
. | 成员选择(对象) | 对象.成员名 | |||
-> | 成员选择(指针) | 对象指针->成员名 | |||
2 | - | 负号运算符 | -表达式 | 右到左 | 单目运算符 |
(类型) | 强制类型转换 | (数据类型)表达式 | |||
++ | 自增运算符 | ++变量名 变量名++ | 单目运算符 | ||
– | 自减运算符 | –变量名 变量名– | 单目运算符 | ||
* | 取值运算符 | *指针变量 | 单目运算符 | ||
& | 取地址运算符 | &变量名 | 单目运算符 | ||
! | 逻辑非运算符 | !表达式 | 单目运算符 | ||
~ | 按位取反运算符 | ~表达式 | 单目运算符 | ||
sizeof | 长度运算符 | sizeof(表达式) | |||
3 | / | 除 | 表达式 / 表达式 | 左到右 | 双目运算符 |
* | 乘 | 表达式*表达式 | 双目运算符 | ||
% | 余数(取模) | 整型表达式%整型表达式 | 双目运算符 | ||
4 | + | 加 | 表达式+表达式 | 左到右 | 双目运算符 |
- | 减 | 表达式-表达式 | 双目运算符 | ||
5 | << | 左移 | 变量<<表达式 | 左到右 | 双目运算符 |
>> | 右移 | 变量>>表达式 | 双目运算符 | ||
6 | > | 大于 | 表达式>表达式 | 左到右 | 双目运算符 |
>= | 大于等于 | 表达式>=表达式 | 双目运算符 | ||
< | 小于 | 表达式<表达式 | 双目运算符 | ||
<= | 小于等于 | 表达式<=表达式 | 双目运算符 | ||
7 | == | 等于 | 表达式==表达式 | 左到右 | 双目运算符 |
!= | 不等于 | 表达式!= 表达式 | 双目运算符 | ||
8 | & | 按位与 | 表达式&表达式 | 左到右 | 双目运算符 |
9 | ^ | 按位异或 | 表达式^表达式 | 左到右 | 双目运算符 |
10 | | | 按位或 | 表达式|表达式 | 左到右 | 双目运算符 |
11 | && | 逻辑与 | 表达式&&表达式 | 左到右 | 双目运算符 |
12 | || | 逻辑或 | 表达式||表达式 | 左到右 | 双目运算符 |
13 | ?: | 条件运算符 | 表达式1? 表达式2: 表达式3 | 右到左 | 三目运算符 |
14 | = | 赋值运算符 | 变量=表达式 | 右到左 | |
/= | 除后赋值 | 变量/=表达式 | |||
*= | 乘后赋值 | 变量*=表达式 | |||
%= | 取模后赋值 | 变量%=表达式 | |||
+= | 加后赋值 | 变量+=表达式 | |||
-= | 减后赋值 | 变量-=表达式 | |||
<<= | 左移后赋值 | 变量<<=表达式 | |||
>>= | 右移后赋值 | 变量>>=表达式 | |||
&= | 按位与后赋值 | 变量&=表达式 | |||
^= | 按位异或后赋值 | 变量^=表达式 | |||
|= | 按位或后赋值 | 变量|=表达式 | |||
15 | , | 逗号运算符 | 表达式,表达式,… | 左到右 |
易错点:
int main()
{
int a = 1, b = 2;
a *= b + 5; //+的优先级高于 *= 所以 a = a * (b + 5) --> a = 7
printf("%d\n", a); //7
}
16.小端存储
小端存储:高位数存放在高地址,低位数存放在低地址;
数值存储和地址存储都遵循小端存储
17.标准输入文件0、标准输出文件1、标准错误输出文件2
当一个程序开始运行时,默认会打开这三个文件;
- 标准输入文件stdin:对应的文件描述符为0,通过某种映射关系将键盘输入映射成标准输入文件;stdin在内存上是有行缓冲区的,当遇到换行(’\n’)才会输入到缓冲区;
- 标准输出文件stdout:对应的文件描述符为1,通过某种映射关系将屏幕输出映射成标准输出文件;stdout在内存上是有行缓冲区的,当遇到换行(’\n’)才会输出到屏幕;
- 标准错误文件stderr:对应的文件描述符为2,是无缓冲区的,是直接输出在屏幕上;
程序案例:从键盘获取字符输出字符个数
18.宏和typedef
#define PINT int* //宏替换,不考虑类型和大小
typedef int* TINT; //类型重命名,会进行类型和大小识别
int main()
{
PING a, b; //int* a, b;
TINT p, q; //int* p; int* q;
}
19.extern关键字
extern用在全局变量或者函数的声明之前,用来说明“此变量、函数是在别处定义的,要在此处引用”;
使用情景:同一个工程下的不同文件
文件fun.c
int g_max = 10;
void fun()
{
g_max +=10;
printf("%d\n", g_max);
}
文件main.c
#include <stdio.h>
extern int g_max;
extern void fun();
int main()
{
int a = g_max;
fun();
}
C++中的extern的其他用法;
20.static关键字的使用
记忆函数:该函数中含有静态局部变量;
静态局部变量:当函数第一次被调用,函数中的局部静态变量被初始化,当这个函数被再次调用时,不会对该静态变量进行初始化,会保留上次函数执行结束后局部变量的值(作用域不变,生存期改变)
-
注意:C语言的静态局部变量只能用常量进行初始化一次;
C++可以用常量和变量进行初始化一次
问题解答:
-
形参能否加上static
答:加上,编译通过,但是该变量是一个“坏”存储类;
所以形参不加static
-
记忆函数是怎样实现第一次初始化的时候调用,后面不调用?
答:在编译阶段,编译器将记忆函数中的静态局部变量存放在.data段中并给该变量一个记录值val = 1,当程序执行到定义静态局部变量的语句时,先对记录值进行判断,如果val == 1说明第一次调用,执行完毕后val–;否则val == 0 ,则跳过这条语句;
- 注意:static int a = 10; 在多线程中需要考虑线程安全,多个线程同时执行该条语句,该值其中的val值会被同时拿到,这样就可能会多次执行该语句。单例模式的问题就需要考虑线程安全
静态全局变量:静态全局变量只能在当前文件中使用(作用域受限制,生存期不变)
-
注意1:当全局变量、函数加上static后,作用域受限于本文件,其他文件无法访问;就算其他文件加上extern关键字声明也无法使用
main.c文件
#include <stdio.h>
extern int g_max;
extern void fun();
int main()
{
int a = g_max; //编译报错,无法解析的命令g_max
fun();
return 0;
}
fun.c文件
static int g_max = 10;
static void fun()
{
printf("%d\n", g_max);
}
注意2:希望fun.c文件中的const int a = 10; 常变量被其他文件调用,就在该变量定义前加上extern,同时使用的文件也要加上该变量的extern声明
main.c文件
#include <stdio.h>
extern int g_max;
int main()
{
int a = g_max;
printf("%d\n", a);
return 0;
}
fun.c文件
extern const int g_max = 10; //外部可见的常变量
//extern static int g_max = 10;
//extern外部可见与static本文件可见矛盾
静态函数:static说明的函数字可以在当前c文件中使用(作用域受限,生存期不变)
21.4G的虚拟空间
22.数据在内存中存放的位置
#include <stdio.h>
int g_maxa = 10 //.data
int g_maxb; //.bss
int g_maxc = 0; //.bss
static int g_maxd; //.bss //默认是0
static int g_maxe = 0; //.bss
static int g_maxf = 10; //.data
int main()
{
int maxa = 10; //编译时期,当作指令存放在.text,运行阶段在栈上通过指令定义变量
int maxb; //.text
int maxc = 0; //.text
static int maxd; //.bss
static int maxe = 0; //.bss
static int maxf = 10; //.data
}
23.const修饰定义的变量和#define宏替换的区别(见5)
-
处理对象不同:const修饰的是定义的变量,而宏替换定义的是常量
-
处理时期不同:const修饰的变量是在编译期间确定,宏替换是在预编译期进行替换;
-
是否占用空间和有类型:const修饰的变量有大小和类型,宏替换的常量不占空间、不具有类型检查
24.浅谈宏函数
就是单纯的替换
#include <stdio.h>
#define SUM(x, y) x*y
int main()
{
int a = 3, b = 4;
int c = SUM(a + 1, b + 2);
//int c = a+1*b+2
printf("%d\n", c);
return 0;
}
//
解决方案
/
#define SUM(x, y) (x)*(y)
25.字符串和字符数组
区别:元素结尾有’\0’是字符串,没有就是字符数组
当定义数组没有给定大小时,同时使用花括号{}进行初始化存在以下两种形式
- 字符串
char arr[] = {
"helloworld"}; //字符串结尾自带'\0'
//sizeof(arr) == 11
//strlen(arr) == 10
- 字符数组(不要使用该种定义方式)
char brr[] = {
'h', 'e', 'l', 'l', 'o', 'w', 'o', 'r', 'l', 'd'};
//结尾没有'\0'
//sizeof(brr) == 10
//strlen(brr) == ???
//这里计算brr的长度的结果是错误的,brr结尾没有'\0'
//strlen()函数从&brr地址开始向后遍历,直到遇见'\0'才会结束,会导致指针越界
拓展:区分字符串常量和字符串
int main()
{
//字符串
char arr[] = {
"hello"};
char brr[] = {
"hello"};
//字符串常量
char* p = "hello";
char* q = "hello";
printf("%d\n", arr == brr);// 0 两个变量的空间地址不同为假
printf("%d\n", p == q); //字符串常量使用的是同一块空间
}
结果:
26.指针移动和*解引用
#include <stdio.h>
int main()
{
//*和++优先级相同,从右向左结合
int arr[5] = {
12, 23, 34, 45, 56};
int* p = arr;
int x = 0;
int y = 0;
x = *p++; //取出p先和*结合,然后p++
y = *p;
printf("%d, %d\n", x, y); // 12 23
x = ++*p; //*p 的值++ 23+1 = 24
y = *p; // 24
printf("%d, %d\n", x, y); //24 24
x = *++p; //++p然后和*结合 34
y = *p; //34
printf("%d, %d\n", x, y); 34 34
return 0;
}
27.指针为什么要有类型
- 指针自增自减操作时,编译器会根据指针的类型计算出移动的步长==sizeof(指针的类型) ;
- 指针在解引用时,会根据指针的类型大小对内存间接访问的空间大小不同;
28.为什么需要二级指针
举个例子:通过下面程序能否成功修改p的指向???
#include <stdio.h>
int tmp = 20;
//指针的值传递,不能改变传递指针的指向
void fun(int* ptr)
{
*ptr = 100;
ptr = &tmp;
//修改的时ptr的指向但并没有修改p的指向
}
int main()
{
int a = 10;
int* p = &a;
fun(p); //ptr = p;等价于 p = &a
printf("a = %d, *p = %d\n", a, *p);
//a = 100, *p = 100
}
结果:不能修改p的指向,修改的是形参ptr的指向,这个时候就需要用到二级指针
二级指针:指向指针的指针。实质也是一个指针,不过二级指针中保存的是一个一级指针的地址;
int tmp = 20;
//二级指针:修改传入指针的指向
void fun(int** ptr)
{
**ptr = 100; //两次解引用,最终结果a = 100;
*ptr = &tmp; //一次解引用,等价于p = &tmp;
/*成功将主函数中的指针p指向了全局部变量tmp,
换句话说就是把tmp的地址存放到p中
*/
}
int main()
{
int a = 10;
int *p = &a;
fun(&p);
//ptr是一个二级指针,所以必须把指针p的地址传进函数,相当于ptr = p,
printf("a = %d, *p = %d\n", a, *p);
//结果 a = 100, b = 20
return 0;
}
29.指针数组和数组指针
- 指针数组:对于下面程序来说,实质是一个存放int*类型的数组,数组中每个元素类型都是int*
int main()
{
int a = 10;
int b = 20;
int* arr[] = {
&a, &b};
//arr[0]存放的是&a, arr[1]存放的是&b
// 解引用
//[]优先级高于* 所以arr[0]得到a的地址,再进行解引用
*arr[0] = 100;
*arr[1] = 200;
printf("a = %d, b = %d\n", a, b);
//结果 a = 100, b = 200;
}
- 数组指针:实际是一个指向数组整体的指针
#include <stdio.h>
int main()
{
int arr[10] = {
1,2,3,4,5,6,7,8,9 };
int* p = &arr[0];
//数组指针:是一个int[10] 类型的指针
int(*parr)[10] = arr;
//等价于int[10] *parr = arr;但是c语言不支持这种写法
printf("%p,%p,%p\n", arr, p, parr);
printf("%p,%p,%p\n", arr + 1, p + 1, parr + 1);
//使用数组指针
for (int i = 0; i < 10; i++)
{
printf("%d ", (*parr)[i]);
}
return 0;
}
结果:
30.const和指针变量的关系
int main()
{
int const a = 10;
//等价于const int a = 10;
int* const p = &a;
//const修饰p,所以不能修改p的指向,*p的值可以修改
int const *p = &a;
//等价于const int *p = &a;
//const修饰*p,不能改变*p的值,可以修改p的指向
}
31.函数指针
实际是一个指向函数入口地址的指针变量;
功能如下:
- 使用函数指针直接调用函数
- 函数指针作为其他函数的形参
代码示例:
#include <stdio.h>
int max(int x, int y)
{
return x > y ? x : y;
}
int main()
{
int a = 10;
int b = 20;
//定义并初始化函数指针
int (*pfun)(int, int) = &max;
//打印函数max的地址
printf("%p\n", pfun);
//使用函数指针
int result = (*pfun)(a, b);
printf("max = %d\n", result);
}
同时对应的指针函数,这个怎莫说呢,就是返回值是一个地址的函数
例如string.h头文件下的strcpy()函数,返回值就是一个char*类型的地址
哼哼~啊啊啊啊啊~结束啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦啦!!!!!!!!!!!!!!!!!!!!!!!!!!!