wordpress 大站,专注东莞微信网站设计,中国做网站最大的公司,做终端客户网站c语言小知识点小计
1、运算符的优先级
运算符的优先级是和指针解引用*的优先级相同的#xff0c;但在代码运行中执行顺序是从后往前的。因此下面代码
int a[10] {1,2,3,4};
int* arr a;
printf(%d,*arr);//访问的值是2
//注意#xff1a;printf(%d运算符的优先级是和指针解引用*的优先级相同的但在代码运行中执行顺序是从后往前的。因此下面代码
int a[10] {1,2,3,4};
int* arr a;
printf(%d,*arr);//访问的值是2
//注意printf(%d,*a);这种语法是错误的因为a是个常量指针常量不可更改2、 strcpy和memcpy的使用
strcpy只用于字符串数组之间的拷贝因为strcpy是以\0来判断结束的。使用方式如下
char a[] {this is a test!};
char b[100];
strcpy(b,a);//不像memcpy需要用户确定拷贝的大小strcpy以\0为结束符判断拷贝的结束memcpy()能够拷贝任意类型的数组但需要手动确定拷贝的大小
int a[10] {1,2,3,4,5};
int b[10];
strcpy(b,a,sizeof(a));//memcpy()需要手动确定拷贝数据的大小3、数组和数组的首地址
由于数组名是一个指针常量因此指针的指向是不能更改的也就是说数组名的指向的地址是不能更改的。因此不能有数组名的操作如下图代码
int a[10] {1,2,3,4} ;
//a这种写法是错误的当需要对数组的元素进行访问时
//可以用下标或者是重新定义一个指针来对数组进行访问。如下所示
//方法一
for (int i 0;i10;i)
{
printf(数组中的元素是%d,a[i]);
}
//方法二,虽然不能使用但是可以使用地址偏移值的方式
for (int i 0;i10;i)
{
printf(数组中的元素是%d,*(ai));
}
//方法三
int *arr a;
for(int i 0;i10;i)
{
printf(数组中的元素是%d*arri)
}4、sizeof的使用
sizeof可以获取当前变量的大小如下面示例所见 如果想获取数组的的所占内存大小只能使用sizeof数组名
int arr[10] {1,2,3,4};
pirnft(数组的大小是%dsizeofa)//获取的是10个int数据占据的空间大小
printf数组指针的大小是%dsizeof(a);//获取的是指针的大小5、指向常量和常量指针
所谓的常量指针是指常量的值不能通过指针来修改但指针的指向是可以修改的 所谓的指针常量是指指针的指向不可修改在第一次绑定赋值后就不能在去绑定其他变量了。示例如下
//指针常量int b1 10;int *const ddd b1; // 指针常量指针的指向不能更改需在初始化的时候就赋值//ddd c1;错误因为指针常量的指向是不能修改的*ddd 20;//语法正确
//常量指针const int *ccc;int c1 20;int d1 30;ccc c1;ccc d1;
// *ccc 40;语法错误常量指针不能用过指针来修改变量的值当可以修改指向cout 数组的大小是: *ccc endl;6、不同系统中变量的大小
在32位系统中指针的大小占4字节32位在64位系统中指针的大小占8字节64位char———1字节 short———2字节 int————4字节 long————4字节 long long——8字节 float————4字节 double———8字节
7、c语言中局部变的内存分配
局部变量在函数执行时在栈区分配空间并在函数执行结束后释放空间。
8、结构体的内存大小与字节对齐
可用手动更改对齐系数#pragma pack(n)n1,2,4,8,16 来改变这一系数其中的 n 就是你要指定的“对齐系数”数据成员对齐规则结构(struct)(或联合(union))的数据成员第一个数据成员放在 offset 为 0 的地方以后每个数据成员的对齐按照 #pragma pack 指定的数值和这个数据成员自身长度中比较小的那个进行。结构(或联合)的整体对齐规则在数据成员完成各自对齐之后结构(或联合)本身也要进行对齐对齐将按照#pragma pack指定的数值和结构(或联合)最大数据成员长度中比较小的那个进行。结合1、2可推断当#pragma pack的n值等于或超过所有数据成员长度的时候这个n值的大小将不产生任何效果。
#includestdio.h
struct S1
{char a;int b;char c;
};//占12字节
struct S2
{int a;char b;int c;char d;
};//占16字节
int main()
{// sizeof功能获取了数据在内存中所占用的存储空间以字节为单位来计数。printf(%d\n, sizeof(struct S1));printf(%d\n, sizeof(struct S2));return 0;
}这里可能不好理解在此对其进行解释假设为32位系统因此当有跨字节时进行4字节对齐下图中每个框代表一个字节由于ch占一个字节因此将其方到第一字节处。由于int 占4字节跨字节了因此进行4字节对齐。short占2个字节因此放到后面的地址空间。由于char占一个字节没跨字节不用字节对齐。 内存对齐的规则
9、数组和指针的区别
char arr1[] “hello”;和char *arr2 “hello”;两句代码中出现的字符串都存储在静态存储区中。char arr1[] 声明了一个名为 arr1 的字符数组这个数组的大小正好能够存放字符串 “hello” 加上一个结尾的空字符 ‘\0’。编译器会为 arr1 在栈上分配足够的空间以存储所有这些字符。字符串字面量 “hello” 存储在程序的常量区。 在函数执行时“hello” 的内容会被复制到新分配的栈空间中也就是 arr1 数组中。 这通常是通过一个循环来完成的每个字符包括结尾的空字符 ‘\0’都会从常量区复制到 arr1 的相应位置。
int main()
{
char arr1[] hello;
char *arr2 hello;
arr1[0] A;//这种写法没有问题因为arr1是存放在栈上的变量。
printf(%c\r\n,arr1[0]);
//arr2[0] A;这种写法是错误的因为arr2是指向静态存储区的一个常量指针不能用过指针来修改值而且“hello”存放在静态存储区里面的值就是不能进行修改的
printf(%c\r\n,arr2[0]);
}10、结构体简介
结构体定义的三种方式:
1、最标准的方式: #include stdio.h
struct student //结构体类型的说明与定义分开。声明
{int age; /*年龄*/float score; /*分数*/char sex; /*性别*/
};
int main ()
{struct student a{ 20,79,f}; //定义printf(年龄%d 分数%.2f 性别%c\n, a.age, a.score, a.sex );return 0;
}2、不环保的方式(声明和定义一起) #include stdio.h
struct student /*声明时直接定义*/
{int age; /*年龄*/float score; /*分数*/char sex; /*性别*//*这种方式不环保只能用一次*/
} a{21,80,n};
int main ()
{printf(年龄%d 分数%.2f 性别%c\n, a.age, a.score, a.sex );
}3、最奈何人的方式定义一次性的结构体变量 #include stdio.h
struct //直接定义结构体变量没有结构体类型名。这种方式最烂
{int age;float score;char sex;
} t{21,79,f};int main ()
{printf(年龄%d 分数%f 性别%c\n, t.age, t.score, t.sex);return 0;
}4、使用typedef定义结构体
用 typedef 定义新类型名来代替已有类型名即给已有类型重新命名
一般格式为typedef 已有类型 新类型名typedef int Elem;
typedef struct{
int date;..........
}STUDENT;
STUDENT stu1,stu2;
//这里也可与在前面加上结构体的名称如
typedef struct student
{
int date;..........
}STUDENT;//在定义结构体时可以直接使用
struct student xiaoming;
//也可以直接使用
STUDENT zhangshan对于结构体的初始化 结构体在没创建实例之前是不分配内存的
typedef struct student{
char name[20];
char sex[20];
int age;
}stu;struct student stu1 {xiaomin,男,11};
//或者初始化时写成
stu stu2 {.name 小张,.sex 男.age 12}结构体之间可以直接赋值这里可以个数组之间不能直接赋值对比记忆 typedef struct {int a;}stu1;stu1 ss1 {10};stu1 ss2 ss1;printf(ss2 %d,ss2.a);结构体中的位域:(但对成员进行读写时花费的时间可能较长) 在成员变量之后使用冒号并声明这个变量将要占用的比特位将大大节省空间。例如你需要9位的一个变量来表示一个数据但c语言中只有uint16但使用u16将浪费7位的内存因此可以写成uint16 sign :9; typedef struct stu
{
uint 8 flag :1;
uint16 num :9;
}STU;11、c语言中的结构体
内存的最小索引单元是1字节那么你其实可以把内存比作一个超级大的「字符型数组」。在上一节我们讲过数组是有下标的我们是通过数组名和下标来访问数组中的元素。那么内存也是一样只不过我们给它起了个新名字地址。每个地址可以存放「1字节」的数据所以如果我们需要定义一个整型变量就需要占据4个内存单元。获取某个变量的地址使用取地址运算符定义指针的时候也使用了*这里属于符号的「重用」也就是说这种符号在不同的地方就有不同的用意在定义的时候表示「定义一个指针变量」在其他的时候则用来「获取指针变量指向的变量的值」。
char* pa a;
int* pb f;
printf(%c, %d\n, *pa, *pb);使用scanf“请输入一个数%d”a这里的需要输入a的地址。可以简单的理解是你想将输入的数据放到内存中的那个地方这个地方的地址是多少。
//如果是使用数组来接收字符串就不需要加
char arr[100];
scanf(%s,arr);//为什么不需要加呢因为数组名就是地址了定义指向数组的指针
char a[10];
char *p;
p a;
p a[0];
//对数组的访问可以通过三中方式printf(*p %d, *(p1) %d, *(p2) %d\n, *p, *(p 1), *(p 2));printf(a %d, a1 %d, a2 %d, *a, *(a1), *(a2));//主要这里不能使用运算来对地址进行改写printf(a %d, a1 %d, a2 %d, a[0], a[1], a[2]);在c语言中字符串结束标识符‘\0’占一个字节
12、指针数组和数组指针
int* p1[5];//指针数组数组中的每一个元素都是int指针类型
int *p2)[5];//数组指针指向一个int类型的数组且数组中有5个元素格式和函数指针相似#include stdio.h
int main(void)
{char* p1[5] {人生苦短我用Python。,PHP是世界上最好的语言,One more thing...,一个好的程序员应该是那种过单行线都要往两边看的人。,C语言很容易让你犯错误C看起来好一些但当你用它时你会发现会死的更惨。};int i;for (i 0; i 5; i){printf(%s\n, p1[i]);//如果想访问每个字符数组的某个元素可以使用p1[i][i]}return 0;
}整形指针指向数组和数组指针的区别
#include
int main(void)
{int temp[5] {1, 2, 3, 4, 5};int(*p2)[5] temp;//这里要注意数组指针指向是数组的地址而不是数组首元素的地址int i;for (i 0; i 5; i){printf(%d\n, *(*p2 i));//因此这里第一次解引用时获取首元素的地址第二次解引用是获取数据}return 0;
}13、指针和二维数组
二维数组在内存中也是线性存放的例如int arry[3][4]其实是申请了一块连续的20字节的地址用于存放数据。array作为数组的名称显然应该表示的是数组的「首地址」。由于二维数组实际上就是一维数组的「线性拓展」因此array应该就是指的指向包含4个元素的数组的指针。指向第一个数组的首地址。 int array[3][4] {{1, 2, 3, 4},{5, 6, 7, 8},{9, 10, 11, 12}};这里注意要访问数据时要么直接通过下标进行访问要么需要通过两次解应用才能访问数据因为第一次解引用获取的是内存数组的地址第二次才是获取内部数组中的数据printf(二维数组中第五个元素数 %d\r\n, array[1][0]);printf(二维数组中第五个元素数 %d\r\n, *(*array4));printf(二维数组中第六个元素数 %d\r\n, *(array[1]1)); 14、void指针 void 不能直接用来定义一个数据类型但可以定义一个指针类型。void 类型的指针可以转换位各种类型的指针。如果void类型没有进行强制类型转换不能用于接收其他类型的指针。 void *a;a malloc(sizeof(int) * 5);for (int i 0; i 5; i){*(((int *)a) i) i;}for (int i 0; i 5; i){printf(-----%d\r\n, *(((int *)a)));这句代码是错误的注意这里对a进行了强制类型转换系统会生成一个临时变量其生存周期为当前执行语句因此无法对其进行操作printf(-----%d\r\n, *((int *)ai));}14、实现函数宏的三种方式
{} 方式这种方式在有if和while判断语句时仍然受判断语句控制
#define INT_SWAP(a,b)\
{ \int tmp a; \a b; \b tmp; \
}
int main()
{
int var_a 1;
int var_b 2;INT_SWAP(var_a, var_b);
printf(var_a %d, var_b %d\n, var_a, var_b); // var_a 2, var_b 1if (1)INT_SWAP(var_a, var_b);
printf(var_a %d, var_b %d\n, var_a, var_b); // var_a 1, var_b 2
}
但当没有花括号时将导致问题问题出现的原因是当没有花括号时判断语句后面本来是只能接一句语的。do{…}while(0) 方式需要注意的是do{}while语句必须使用分号结尾
#define INT_SWAP(a,b) \
do{ \if (a 0 || b 0) \break; \int tmp a; \a b; \b tmp; \
}while(0)//因此下面代码将有问题if (1)
if (1)INT_SWAP(var_a, var_b)//按正常思维来说这里确实不应该有分号但由于这个宏定义是do{}while结尾因此c语言要求必须加上冒号
else
{printf(hello world!\n);
}({}) 方式
与 do{...}while(0) 相同({}) 支持在无花括号且有分支的 if 语句中直接调用。例如
#define INT_SWAP(a,b) \
({ \int tmp a; \a b; \b tmp; \
})
C 语言规定 ({}) 中的最后一条语句的结果为该双括号体的返回值
int a ({1,2,3});//根据运算的优先级先算后面的括号中内容算完后返回315、在c 中指定使用c语言的方式编译
兼容性C语言支持函数重载而C语言不支持。C编译器在编译时会对函数名进行名称修饰name mangling以支持重载而C编译器不会。使用extern C可以确保C代码中声明的函数或变量在链接时使用C语言的名称修饰规则这样C程序就能正确地调用C库中的函数。链接C库当你想在C程序中使用C库例如标准C库或第三方C库时你需要使用extern C来声明这些库中的函数这样C编译器才知道如何正确地处理这些函数的名称
extern C {// 这里可以包含C头文件或者直接声明C函数#include c_header.h// 或者int c_function(int arg);
}
-------------------------------------------------------------------------------------------------
// C程序
#include iostream
extern C {#include c_lib.h//这个头文件中包含的有addintint函数
}int main() {int result add(5, 3); // 正确调用C库中的函数std::cout The sum is: result std::endl;return 0;
}
16、位域
在结构体定义时我们可以指定某个成员变量所占用的二进制位数Bit这就是位域。请看下面的例子 位域定义的结构体也存在内存对齐当前指定的位域小于当前的数据类型时或者没有超过系统定义最小对齐字节时则紧挨着存储否则进行字节对齐
typedef struct bs{unsigned m;unsigned n: 4;unsigned char ch: 6;
};
C语言标准还规定只有有限的几种数据类型可以用于位域。
在 ANSI C 中这几种数据类型是 int、signed int 和
unsigned intint 默认就是 signed int到了 C99_Bool 也被支持了。
如果成员之间穿插着非位域成员那么不会进行压缩。例如对于下面的 bs
1struct bs{
2 unsigned m: 12;
3 unsigned ch;
4 unsigned p: 4;
5};17、#error的目的是什么
可以提醒程序员定义重复定义的某些变量
#define a 10
#ifdef a
#error 请取消定义a
#endif18、c语言中使用的3种死循环
while(1)
{}for(;;)
{}do{}while(1);loop:...goto loop;19、static关键字
在C语言中关键字static有三个明显的作用 第一、在修饰变量的时候static修饰的静态局部变量只执行一次而且延长了局部变量的生命周期直到程序运行结束以后才释放。 第二、static修饰全局变量的时候这个全局变量只能在本文件中访问不能在其它文件中访问即便是extern外部声明也不可以。 第三、static修饰一个函数则这个函数的只能在本文件中调用不能被其他文件调用。Static修饰的局部变量存放在全局数据区的静态变量区。初始化的时候自动初始化为0 1不想被释放的时候可以使用static修饰。比如修饰函数中存放在栈空间的数组。如果不想让这个数组在函数调用结束释放可以使用static修饰 2考虑到数据安全性当程想要使用全局变量的时候应该先考虑使用static 20、关键字volatile有什么含意并给出三个不同的例子。
一个定义为volatile的变量是说这变量可能会被意想不到地改变这样编译器就不会去假设这个变量的值了。精确地说就是优化器在用到这个变量时必须每次都小心地重新读取这个变量的值而不是使用保存在寄存器里的备份。下面是volatile变量的几个例子
1并行设备的硬件寄存器如状态寄存器 2一个中断服务子程序中会访问到的非自动变量(Non-automatic variables) 3多线程应用中被几个任务共享的变量
21、在嵌入式系统中将a的第三位置一和清零
#define BIT3 (0x1Ul 3)
static int a;void set_bit3(void)
{a | BIT3;
}
void clear_bit3(void)
{a ~BIT3;
}22、c语言中的__interrupt关键字
使用这个函数定义一个中断服务子程序ISR
函数存在的问题
①函数不能有返回值
②函数不能有参数
③不要在中断服务函数中做浮点运算
④不要在中断服务函数中进行耗时的操作和不可重入函数在C语言中函数的重入性reentrancy
是指一个函数在执行过程中可以被中断并且在稍后重新开始执行而不会产生错误的能力。
重入函数和不可重入函数的概念主要与多线程编程和中断处理相关。
__interrupt double compute_area (double radius)
{double area PI * radius * radius;printf(\nArea %f, area);return area;
}
23、c语言中的自动类型转换
是当表达式中存在有符号类型和无符号类型时所有的操作数都自动转换为无符号类型。
void foo(void)
{unsigned int a 6;int b -20;(ab 6) ? puts( 6) : puts( 6);
}//结果是大于624、使用typedef定义指针类型时需要注意不能让连续定义两个指针类型否则第二个定义失败
#define dPS struct s *
typedef struct s * tPS;
dPS p1,p2;
tPS p3,p4;
第一个扩展为
struct s * p1, p2;25、c语言中与内存相关的错误
间接应用坏指针
scanf(%d, value);//正确的写法\
scanf(%d, value);//系统会讲value里面的值当成地址将数据写到这个地址中读未初始化的内存
int *a (int *)malloc(5*sizeof(int));
for(int i 0;i5;i)
{a[i]i;//这句代码是不合法的因为在申请空间后并没有对空间中的数据进行初始化因此不能将里面的数据看成0
}栈缓冲区溢出
void buff()
{char buf[6];//由于当前定义的缓冲区较小因此下面定的gets输入的字符不能大于这个值否则会发生溢出虽然在vs中运行没有报错但当前已经发生的栈溢出。gets(buf);return;
}假设指针和它们指向的对象大小相同
int **makeArray(int n, int m)
{int i;int **A (int **)malloc(n * sizeof(int)); /* 注意此处语句存在问题 */for(i 0; i n; i){A[j] (int *)malloc(m * sizeof(int));}return A;
}
此程序的目的是创建一个由 n 个指针组成的数组每个指针都指向一个包含 m 个 int 的数组。
然而第 4 行程序代码将 sizeof(int *)写成了 sizeof(int)代码实际上创建的是一个 int 的数组。
这段代码只有在 int 和指向 int 的指针大小相同的机器上运行良好否则就会出现错误。内存越界 代码试图访问申请空间之外的空间将导致内存越界
int **makeArray(int n, int m)
{int i;int **A (int **)malloc(n * sizeof(int*)); /* 注意此处语句存在问题 */for(i 0; i n; i) /* 注意循环终止条件这里访问的空间总大小为n1将造成空间越界 */{A[j] (int *)malloc(m * sizeof(int));}return A;
}* 和 运算符的优先级 *p 等价于*p 如果想对指针指向的值进行自增操作需要用括号进行优先级控制 *p 这个相当于对指针指向的值进行自加 寻找数组中的特定值
int * secrch(int *p,int value)
{
while(*p *p ! value)//第一分p判断当前的值是否为空第二个用来判断当前的值是否为目标值
{p;
}
return p;
}使用malloc等函数后没有释放空间造成内存泄
void leak(int n)
{int *x (int *)malloc(n * sizeof(int));return;
}26、c语言中几种分配内存的函数
mallocsize以直接为单位分配未初始化的空间calloc(number,size)以字节为单位分配number个每个大小为size的空间并将数据初始化为0reallocvoid *ptrsize重新分配ptr的大小如果ptr null则相当于malloc函数如果size 0相当free函数
27、c语言中内存拷贝的内存重叠现象
使用memcpy目的源大小拷贝内存中的数据时可能由于内存重叠而造成影响使用memove目的源大小不会因为内存重叠而造成数据混乱
void *Memcpy2(void *dest, const void *src, size_t count)
{ char *d; const char *s; if (((int)dest ((int)srccount)) || (dest src)) { d (char*)dest; s (char*)src; while (count--) *d *s; } else /* overlap :存在内存的重叠*/ { d (char *)((int)dest count - 1); /* 指针位置从末端开始注意偏置 */ s (char *)((int)src count -1); while (count --) *d-- *s--; } return dest;
} 28、内存分配函数 malloc 原理及实现
虚拟内存地址与物理内存地址 为了简单现代操作系统在处理内存地址时普遍采用虚拟内存地址技术。即在汇编程序或机器语言层面当涉及内存地址时都是使用虚拟内存地址。采用这种技术时每个进程仿佛自己独享一片2N字节的内存其中N是机器位数。例如在64位CPU和64位操作系统下每个进程的虚拟地址空间为264Byte。 由于在机器语言层面都是采用虚拟地址当实际的机器码程序涉及到内存操作时需要根据当前进程运行的实际上下文将虚拟地址转换为物理内存地址才能实现对真实内存数据的操作。 页与地址的构成 在现代操作系统中不论是虚拟内存还是物理内存都不是以字节为单位进行管理的而是以页Page为单位。一个内存页是一段固定大小的连续内存地址的总称具体到Linux中典型的内存页大小为4096Byte4K。 由于每页的大小都是4k 4096字节因此寻址时只需要12位就可以完成因此每个地址用页号和偏移号来代表在内存中的位置