关于我的编程语言——C/C++——第三篇
(叠甲:如有侵权请联系,内容都是自己学习的总结,一定不全面,仅当互相交流(轻点骂)我也只是站在巨人肩膀上的一个小卡拉米,已老实,求放过)
操作符详解
操作符和表达式
算数操作符
+ - * / %
1)除了%操作符以外,其他的几个操作符可以作用于整数和浮点数
2)对于/操作符如果两个操作数都为整数,执行整数除法。而只要有浮点数执行的就是浮点数除法
3)%操作符的两个操作数必须为整数,返回的是整数之后的余数
移位操作符
<< 左移操作符
>> 右移操作符
左移操作符 移位规则:
左移抛弃,右边补零
右移操作符 移位规则:
1)逻辑移位,左边用0填充,右边丢弃
2)算数移位,左边用原该值的符号位填充,右边丢弃
注:对于移位操作符,不要移动负数位,这个是标准为定义的
int num = 10;
num>>-1;
位操作符
& //按位与
| //按位或
^ //按位异或
注:它们的操作数必须是整数
例1:不创建临时变量,实现两个数的交换
#include <stdio.h>
int main()
{
int a = 10; //00000000 00000000 00000000 00001010
int b = 20; //00000000 00000000 00000000 00010100
a = a ^ b; //00000000 00000000 00000000 00011110
b = a ^ b; //00000000 00000000 00000000 00001010
a = a ^ b; //00000000 00000000 00000000 00010100
printf("a = %d b = %d\n", a, b);
return 0;
}
赋值操作符
+=
-=
*=
/=
%=
>>=
<<=
&=
|=
^=
这些运算符可以写出复合效果
int x = 10;
x = x+10;
x += 10;//复合赋值
//其他运算符一样的道理。这样写更加简洁
单目操作符
! 逻辑反操作
- 负值
+ 正值
& 取地址
sizeof 操作数的类型长度(以字节为单位)
~ 对一个数的二进制按位取反
-- 前置、后置--
++ 前置、后置++
* 间接访问操作符(解引用操作符)
(类型) 强制类型转换
++ 和--运算符
//前置++和--:
#include <stdio.h>
int main()
{
int a = 10;
int x = ++a;
//先对a进行自增,然后对使用a,也就是表达式的值是a自增之后的值。x为11。
int y = --a;
//先对a进行自减,然后对使用a,也就是表达式的值是a自减之后的值。y为10;
return 0;
}
//后置++和--
#include <stdio.h>
int main()
{
int a = 10;
int x = a++;
//先对a先使用,再增加,这样x的值是10;之后a变成11;
int y = a--;
//先对a先使用,再自减,这样y的值是11;之后a变成10;
return 0;
}
关系操作符
>
>=
<
<=
!= 用于测试“不相等”
== 用于测试“相等
逻辑操作符
&& 逻辑与
|| 逻辑或
区分逻辑与和按位与区分逻辑或和按位或
1&2----->0
1&&2---->1
1|2----->3
1||2---->1
逗号表达式
exp1, exp2, exp3, …expN
逗号表达式,就是用逗号隔开多个表达式,逗号表达式,从左向右依次执行,整个表达式的结果是最后一个表达式的结果。
#include<stdio.h>
int main()
{
int a = 1;
int b = 2;
int c = (a > b, a = b + 10, a, b = a + 1);//逗号表达式
printf("%d\n",c);
return 0;
}
下标引用,函数调用和结构成员
1) [ ]下标引用操作符
操作数:一个数组名+一个索引值
int arr[10];//创建数组
arr[9] = 10;//实用下标引用操作符。
[ ]的两个操作数是arr和9。
2)()函数调用操作符,接受一个或者多个操作数;第一个操作数是函数名,剩余的操作数就是传递给函数的参数。
#include <stdio.h>
void test1()
{
printf("hehe\n");
}
void test2(const char *str)
{
printf("%s\n", str);
}
int main()
{
test1(); //实用()作为函数调用操作符。
test2("hello bit.");//实用()作为函数调用操作符。
return 0;
}
3)访问一个结构体成员
.结构体.成员名
->结构体指针->成员名
#include <stdio.h>
struct Stu
{
char name[10];
int age;
char sex[5];
double score;
};
void set_age1(struct Stu stu)
{
stu.age = 18;
}
void set_age2(struct Stu* pStu)
{
pStu->age = 18;//结构成员访问
}
int main()
{
struct Stu stu;
struct Stu* pStu = &stu;//结构成员访问
stu.age = 20;//结构成员访问
set_age1(stu);
pStu->age = 20;//结构成员访问
set_age2(pStu);
return 0;
}
表达式求值
表达式求职的顺序一部分是由操作符的优先级和结核性决定,同样,有些表达式的操作数在求值的过程中可能需要转换成其他类型。
隐式类型转换
C语言的整型算术运算总是至少以缺省整型的精度来进行的,为了获得这个精度,表达式中的字符和短整型操作数在使用之前被转换为普通整型,这种转换称为整型提升。
整型提示的意义:
表达式的整型运算要在CPU的相应运算器件内执行,CPU整型运算器(ALU)的操作数的字节长度一般就是int的字节长度,同时也是CPU的通用寄存器的长度。
因此,即使两个char类型相加,在CPU执行实际上也要转换成CPU内整型操作数的标准长度。通常CPU很难实现8比特一字节直接相加的运算,所以,表达式中各种长度可能小于int长度的整型值,都必须先转换成int或者unsigned int ,然后才能送入CPU去执行运算。
//实例1
char a,b,c;
...
a = b + c;
b和c的值被提升为普通整型,然后再执行加法运算,加法运算完成之后,结果将被截断,然后再存储于a中。
整型提升是按照变量的数据类型的符号位来提升的
//负数的整形提升
char c1 = -1;
变量c1的二进制位(补码)中只有8个比特位:
1111111
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为1
提升之后的结果是:
11111111111111111111111111111111
//正数的整形提升
char c2 = 1;
变量c2的二进制位(补码)中只有8个比特位:
00000001
因为 char 为有符号的 char
所以整形提升的时候,高位补充符号位,即为0
提升之后的结果是:
00000000000000000000000000000001
//无符号整形提升,高位补0
例1:
#include<stdio.h>
int main()
{
char a = 0xb6; //1011 0110
short b = 0xb600; //1011 0110 0000 0000
int c = 0xb6000000; //1011 0110 0000 0000 0000 0000 0000 0000
if (a == 0xb6) //1111 1111 1111 1111 1111 1111 1011 0110
printf("a");
if (b == 0xb600) //1111 1111 1111 1111 1011 0110 0000 0000
printf("b");
if (c == 0xb6000000)//1011 0110 0000 0000 0000 0000 0000 0000
printf("c");
return 0;
}
其中的a,b要整型提升,但是c不需要整型提升,a,b整型提升之后,变成了负数,所以表达式a==0xb6,b== oxb600的结果未假,但c不用提升,所以就打印c
例2:只要参与计算就要进行整型提升
#include<stdio.h>
int main()
{
char c = 1;
printf("%u\n", sizeof(c));
printf("%u\n", sizeof(+c));
printf("%u\n", sizeof(-c));
return 0;
}
算数转换
如果某个操作符的各个操作数属于不同类型,那么除非其中一个操作数的转换为另外一个类型,否则操作就无法进行,下面的层次体系称为寻常算数转换
long double
double
float
unsigned long int
long int
unsigned int
int
注:算数转换要合理,要不然会有一些潜在的问题。
float f = 3.14;
int num = f;//隐式转换,会有精度丢失
操作符的属性
复杂表达式的求值有三个影响的因素
1)操作符的优先级
2)操作符的结合性
3)是否控制求值顺序
一些特例
a*b + c*d + e*f
注:代码1在计算的时候,由于*比+的优先级高,只能保证,第一个*的计算比+早,但优先级并不能决定第三个*比第一个+早执行。
除此以外还要很多其他的。
结论:我们写出的表达式如果不能通过操作符的属性确定唯一的计算路径,那这个表达式就是存在问题的。
指针
什么是指针?
指针(pointer)是编程语言中的一个对象,利用地址,它的值直接指向(points to)存在电脑存储中另一个地方的值,由于通过地址能找到所需的变量单元,可以说,地址指向该变量单元。因此,将地址形象化的称为指针,意思就是通过它能找到以它为地址的内存单元。
指针是个变量,存放内单元地址(编号)
#include <stdio.h>
int main()
{
int a = 10;//在内存中开辟一块空间
int *p = &a;//这里我们对变量a,取出它的地址,可以使用&操作符。
//将a的地址存放在p变量中,p就是一个之指针变量。
return 0;
}
总结:指针就是变量,用来存放地址的变量。(存放在指针中的值都被当成地址处理)。
一个小的单元1字节,它是如何编码的?
对于32位的机器,假设我们有32根地址线,那么假设每根地址在寻址的时候产生一个电信号正电/负电(1或0),这样就会有byte==4G个地址。64位同理。
这里我们就可以明白
1)在32位的机器上,地址是32个0和1混合组成的二进制序列,那地址就得用4个字节的空间来存储,所以一个指针变量的大小就是4个字节。
2)那如果在64位机器上,如果64个地址线,一个指针变量的大小就是8个字节。
总结:
1)指针是用来存放地址的,地址是唯一表示一块地址空间的。
2)指针的大小在32位平台是4个字节,在64位平台是8个字节。
指针和指针类型
char *pc = NULL;
int *pi = NULL;
short *ps = NULL;
long *pl = NULL;
float *pf = NULL;
double *pd = NULL;
这里可以看到,指针的定义方式是:type +*,其实:char*类型的指针是为了存放,char类型变量的地址。short*类型的指针是为了存放short类型变量的地址,int*类型的指针是为了存放int类型变量的地址。
指针类型的意义
指针+-整数
#include <stdio.h>
//演示实例
int main()
{
int n = 10;
char* pc = (char*)&n;
int* pi = &n;
printf("%p\n", &n);
printf("%p\n", pc);
printf("%p\n", pc + 1);
printf("%p\n", pi);
printf("%p\n", pi + 1);
return 0;
}
总结:指针的类型决定了指针向前或向后走一步有多大(距离)
指针的解引用
#include <stdio.h>
int main()
{
int n = 0x11223344;
char* pc = (char*)&n;
int* pi = &n;
*pc = 0; //重点在调试的过程中观察内存的变化。
*pi = 0; //重点在调试的过程中观察内存的变化。
return 0;
}
总结:指针的类型决定了,对指针解引用的时候有多大权限(能操作几个字节)。比如:char*的指针解引用就只能访问一个字节,而int*的指针的解引用就能访问四个字节。
野指针
指针指向的位置不可知(随机的、不正确的,没有明确限制的)指针变量在定义时如果为初始化,其值就是随机的,指针变量的值是别的变量的地址,意味着指针指向了一个地址是不确定的变量,此时去解引用就是去访问了一个不确定的地址,结果是不可知的。
野指针的成因
1)指针未初始化
#include <stdio.h>
int main()
{
int *p;//局部变量指针未初始化,默认为随机值
*p = 20;
return 0;
}
2)指针越界访问
#include <stdio.h>
int main()
{
int arr[10] = {0};
int *p = arr;
int i = 0;
for(i=0; i<=11; i++)
{
//当指针指向的范围超出数组arr的范围时,p就是野指针
*(p++) = i;
}
return 0;
}
3)指针指向的空间释放(后面重点讲)
如何规避野指针
1)指针初始化
2)小心指针越界
3)指针指向空间释放及时置NULL
4)指针使用之前检查有效性
指针运算
1)指针+ -整数
2)指针 - 指针
3)指针的关系运算
指针+ -整数
#define N_VALUES 5
float values[N_VALUES];
float *vp = NULL;
//指针+-整数;指针的关系运算
for (vp = &values[0]; vp < &values[N_VALUES];)
{
*vp++ = 0;
}
指针-指针
int my_strlen(char *s)
{
char *p = s;
while(*p != '\0' )
p++;
return p-s;
}
指针的关系运算
//写法1
for(vp = &values[N_VALUES]; vp > &values[0];)
{
*--vp = 0;
}
//写法2
for(vp = &values[N_VALUES-1]; vp >= &values[0];vp--)
{
*vp = 0;
}
实际在绝大部分的编译器上是可以顺利完成任务的,然而我们还是应该避免这样写,因为标准并不保证它可行。
标准规定:
允许指向数组元素的指针与指向数组最后一个元素后面的哪个内存位置的指针比较,但是不允许与指向第一个元素之前的那个内存位置的指针进行比较。
指针和数组
#include <stdio.h>
int main()
{
int arr[10] = {1,2,3,4,5,6,7,8,9,0};
printf("%p\n", arr);
printf("%p\n", &arr[0]);
return 0;
}
可以看到数组名和数组首元素的地址是一样的,所以,数组名表示的是数组首元素的地址。
例:
#include <stdio.h>
int main()
{
int arr[] = { 1,2,3,4,5,6,7,8,9,0 };
int* p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
for (int i = 0; i < sz; i++)
{
printf("&arr[%d] = %p <====> p+%d = %p\n", i, &arr[i], i, p + i);
}
return 0;
}
例2:
#include <stdio.h>
int main()
{
int arr[] = { 1, 2, 3, 4, 5, 6, 7, 8, 9, 0 };
int* p = arr; //指针存放数组首元素的地址
int sz = sizeof(arr) / sizeof(arr[0]);
int i = 0;
for (i = 0; i < sz; i++)
{
printf("%d ", *(p + i));
}
return 0;
}
二级指针
指针变量也是变量,是变量就有地址,那指针变量的地址存放在哪里?——二级指针
对二级指针的运算有:
1)*ppa通过ppa中的地址进行解引用,这样找到的是pa,*ppa其实访问的是pa。
int b = 20;
*ppa = &b;//等价于 pa = &b;
2)**ppa先通过*ppa找到pa,然后对pa进行解引用操作:*pa,那找到的是a。
**ppa = 30;
//等价于*pa = 30;
//等价于a = 30;
指针数组
这是一个数组,和整型数组,字符数组一样,只不过其中存储的是指针。
结构体
结构体的声明
结构体是一些值的集合,这些值称为成员变量,结构的每个成员可以是不同类型的变量。
语法
struct tag
{
member-list;
}variable-list;
例:
typedef struct Stu
{
char name[20];//名字
int age;//年龄
char sex[5];//性别
char id[20];//学号
}Stu;//分号不能丢
结构成员的类型
结构成员可以是标量、数组、指针、或者其他结构体
结构体变量的定义和初始化
struct Point
{
int x;
int y;
}p1; //声明类型的同时定义变量p1
struct Point p2; //定义结构体变量p2
//初始化:定义变量的同时赋初值。
struct Point p3 = {x, y};
struct Stu //类型声明
{
char name[15];//名字
int age; //年龄
};
struct Stu s = {"zhangsan", 20};//初始化
struct Node
{
int data;
struct Point p;
struct Node* next;
}n1 = {10, {4,5}, NULL}; //结构体嵌套初始化
struct Node n2 = {20, {5, 6}, NULL};//结构体嵌套初始化
结构体成员的访问
1)结构体变量访问成员,结构变量的成员是通过点操作符(.)访问的,点操作符接受两个操作数。
struct S s;
strcpy(s.name, "zhangsan");//使用.访问name成员
s.age = 20;//使用.访问age成员
2)结构体指针访问指向变量的成员 我们只有指向结构体的指针,可以使用(->)操作符。
#include<stdio.h>
struct Stu
{
char name[20];
int age;
};
void print(struct Stu* ps)
{
printf("name = %s age = %d\n", (*ps).name, (*ps).age);
//使用结构体指针访问指向对象的成员
printf("name = %s age = %d\n", ps->name, ps->age);
}
int main()
{
struct Stu s = { "zhangsan", 20 };
print(&s);//结构体地址传参
return 0;
}
结构体传参
#include<stdio.h>
struct S
{
int data[1000];
int num;
};
struct S s = { {1,2,3,4}, 1000 };
//结构体传参
void print1(struct S s)
{
printf("%d\n", s.num);
}
//结构体地址传参
void print2(struct S* ps)
{
printf("%d\n", ps->num);
}
int main()
{
print1(s); //传结构体
print2(&s); //传地址
return 0;
}
首选print2,函数传参的时候,如果不是地址的话,就会有临时拷贝,如果结构体过大的话,系统开销就会很大,导致性能下降。
over ,see you next time!