位运算主要有六种,如下图所示:
位运算符作用于位,并逐位执行操作。(个人见解:位运算和数值计算不同,位运算针对位进行运算,而一般的C语言中数值计算是直接进行赋值运算的)&、 | 和 ^ (三个主要的位运算)的真值表如下所示:
p | q | p & q | p | q | p ^ q |
---|---|---|---|---|
0 | 0 | 0 | 0 | 0 |
0 | 1 | 0 | 1 | 1 |
1 | 1 | 1 | 1 | 0 |
1 | 0 | 0 | 1 | 1 |
个人而言觉得可以这么理解:位运算与相当于乘法,位运算或相当于加法,位运算异或相当于一个比较(如果相同,则值为0,若不同,则值为1),位运算取反的话就相当于一种全部变换(0变成1,1变成0),位运算左移右移本质上是一样的,相当于将原来的编码进行整体向左平移或者向右平移。
如下是一个非常便于理解的例子:
假设如果 A = 60,且 B = 13,现在以二进制格式表示,它们如下所示:
A = 0011 1100
B = 0000 1101
-----------------
A&B = 0000 1100
A|B = 0011 1101
A^B = 0011 0001
~A = 1100 0011
下表显示了 C 语言支持的位运算符。假设变量 A 的值为 60,变量 B 的值为 13,则:
运算符 | 描述 | 实例 |
---|---|---|
& | 按位与操作,按二进制位进行"与"运算。运算规则: |
(A & B) 将得到 12(十进制数值),即为 0000 1100 |
| | 按位或运算符,按二进制位进行"或"运算。运算规则: |
(A | B) 将得到 61(十进制数值),即为 0011 1101 |
^ | 异或运算符,按二进制位进行"异或"运算。运算规则: |
(A ^ B) 将得到 49(十进制数值),即为 0011 0001 |
~ | 取反运算符,按二进制位进行"取反"运算。运算规则: |
(~A ) 将得到 -61(十进制数值),即为 1100 0011,一个有符号二进制数的补码形式。 |
<< | 二进制左移运算符。将一个运算对象的各二进制位全部左移若干位(左边的二进制位丢弃,右边补0)。 | A << 2 将得到 240(十进制数值),即为 1111 0000 |
>> | 二进制右移运算符。将一个数的各二进制位全部右移若干位,正数左补0,负数左补1,右边丢弃。 | A >> 2 将得到 15(十进制数值),即为 0000 1111 |
实例:
#include
int main()
{
unsigned int a = 60; /* 60 = 0011 1100 */
unsigned int b = 13; /* 13 = 0000 1101 */
int c = 0;
c = a & b; /* 12 = 0000 1100 */
printf("Line 1 - c 的值是 %d\n", c );
c = a | b; /* 61 = 0011 1101 */
printf("Line 2 - c 的值是 %d\n", c );
c = a ^ b; /* 49 = 0011 0001 */
printf("Line 3 - c 的值是 %d\n", c );
c = ~a; /*-61 = 1100 0011 */
printf("Line 4 - c 的值是 %d\n", c );
c = a << 2; /* 240 = 1111 0000 */
printf("Line 5 - c 的值是 %d\n", c );
c = a >> 2; /* 15 = 0000 1111 */
printf("Line 6 - c 的值是 %d\n", c );
}
输出结果如下:
Line 1 - c 的值是 12
Line 2 - c 的值是 61
Line 3 - c 的值是 49
Line 4 - c 的值是 -61
Line 5 - c 的值是 240
Line 6 - c 的值是 15
参考网址:https://www.runoob.com/cprogramming/c-operators.html
个人感觉:static就是定义一个静态变量,使之不能够被全局调用,只起一次作用。
我们知道在函数内部定义的变量,当程序执行到它的定义处时,编译器为它在栈上分配空间,函数在栈上分配的空间在此函数执行结束时会释放掉,这样就产生了一个问题: 如果想将函数中此变量的值保存至下一次调用时,如何实现? 最容易想到的方法是定义为全局的变量,但定义一个全局变量有许多缺点,最明显的缺点是破坏了此变量的访问范围(使得在此函数中定义的变量,不仅仅只受此函数控制)。static 关键字则可以很好的解决这个问题。另外,在 C++ 中,需要一个数据对象为整个类而非某个对象服务,同时又力求不破坏类的封装性,即要求此成员隐藏在类的内部,对外不可见时,可将其定义为静态数据。
静态全局变量有以下特点:
优点:静态全局变量不能被其它文件所用;其它文件中可以定义相同名字的变量,不会发生冲突。
全局变量和全局静态变量的区别
静态局部变量有以下特点:
一般程序把新产生的动态数据存放在堆区,函数内部的自动变量存放在栈区。自动变量一般会随着函数的退出而释放空间,静态数据(即使是函数内部的静态局部变量)也存放在全局数据区。全局数据区的数据并不会因为函数的退出而释放空间。
实例
#include
#include
int k1 = 1;
int k2;
static int k3 = 2;
static int k4;
int main()
{
static int m1 = 2, m2;
int i = 1;
char*p;
char str[10] = "hello";
char*q = "hello";
p = (char *)malloc(100);
free(p);
printf("栈区-变量地址 i:%p\n", &i);
printf("栈区-变量地址 p:%p\n", &p);
printf("栈区-变量地址 str:%p\n", str);
printf("栈区-变量地址 q:%p\n", &q);
printf("堆区地址-动态申请:%p\n", p);
printf("全局外部有初值 k1:%p\n", &k1);
printf(" 外部无初值 k2:%p\n", &k2);
printf("静态外部有初值 k3:%p\n", &k3);
printf(" 外静无初值 k4:%p\n", &k4);
printf(" 内静态有初值 m1:%p\n", &m1);
printf(" 内静态无初值 m2:%p\n", &m2);
printf(" 文字常量地址:%p, %s\n", q, q);
printf(" 程序区地址:%p\n", &main);
return 0;
}
输出结果如下:
补充:在类中,static 可以用来修饰静态数据成员和静态成员方法。
静态数据成员
(1)静态数据成员可以实现多个对象之间的数据共享,它是类的所有对象的共享成员,它在内存中只占一份空间,如果改变它的值,则各对象中这个数据成员的值都被改变。
(2)静态数据成员是在程序开始运行时被分配空间,到程序结束之后才释放,只要类中指定了静态数据成员,即使不定义对象,也会为静态数据成员分配空间。
(3)静态数据成员可以被初始化,但是只能在类体外进行初始化,若未对静态数据成员赋初值,则编译器会自动为其初始化为 0。
(4)静态数据成员既可以通过对象名引用,也可以通过类名引用。
静态成员函数
(1)静态成员函数和静态数据成员一样,他们都属于类的静态成员,而不是对象成员。
(2)非静态成员函数有 this 指针,而静态成员函数没有 this 指针。
(3)静态成员函数主要用来方位静态数据成员而不能访问非静态成员。
总结:
(1)静态成员函数中不能调用非静态成员。
(2)非静态成员函数中可以调用静态成员。因为静态成员属于类本身,在类的对象产生之前就已经存在了,所以在非静态成员函数中是可以调用静态成员的。
(3)静态成员变量使用前必须先初始化(如 int MyClass::m_nNumber = 0;),否则会在 linker 时出错。
参考网址:https://www.runoob.com/w3cnote/cpp-static-usage.html
个人感觉:extern变量和static变量正好是相反的,static多用于定义静态变量,使之不能被全局调用,只能单次在一个文件里被调用;而extern则多用于定义全局变量,是指可以被全局调用甚至跨文件调用,多次被调用
在定义变量的时候,这个extern居然可以被省略(定义时,默认均省略);在声明变量的时候,这个extern必须添加在变量前,所以有时会让你搞不清楚到底是声明还是定义。或者说,变量前有extern不一定就是声明,而变量前无extern就只能是定义。注:定义要为变量分配内存空间;而声明不需要为变量分配内存空间。
(1)变量
实例:
extern int a; // 声明一个全局变量 a
int a; // 定义一个全局变量 a
extern int a =0 ; // 定义一个全局变量 a 并给初值。
int a =0; // 定义一个全局变量 a, 并给初值,
第四个等于第三个,都是定义一个可以被外部使用的全局变量,并给初值。
糊涂了吧,他们看上去可真像。但是定义只能出现在一处。也就是说,不管是int a;还是extern int a=0;还是int a=0;都只能出现一次,而那个extern int a可以出现很多次。
当你要引用一个全局变量的时候,你就必须要声明,extern int a; 这时候extern不能省略,因为省略了,就变成int a;这是一个定义,不是声明。注:extern int a; 中类型 int 可省略,即 extern a; 但其他类型则不能省略。
(2)函数
对于函数也一样,也是定义和声明,定义的时候用extern,说明这个函数是可以被外部引用的,声明的时候用extern说明这是一个声明。 但由于函数的定义和声明是有区别的,定义函数要有函数体,声明函数没有函数体(还有以分号结尾),所以函数定义和声明时都可以将extern省略掉,反正其他文件也是知道这个函数是在其他地方定义的,所以不加extern也行。两者如此不同,所以省略了extern也不会有问题。
实例:
/*某cpp文件*/
int fun(void)
{
return 0;
}
很好,我们定义了一个全局函数:
/*另一cpp文件*/
int fun(void);
我们对它做了个声明,然后后面就可以用了, 加不加extern都一样, 我们也可以把对 fun 的声明放在一个头文件里,最后变成这样:
/*fun.h*/
int fun(void); //函数声明,所以省略了extern,完整些是extern int fun(void);
/*对应的fun.cpp文件*/
int fun(void)
{
return 0;
}//一个完整的全局函数定义,因为有函数体,extern同样被省略了。
然后,一个要使用你的fun的客户,把这个头文件包含进去,ok,一个全局的声明。没有问题。但是,,如果是这个客户要使用全局变量,那么要extern 某某变量,不然就成了定义了。
总结:
对变量而言,如果你想在本源文件(例如文件名A)中使用另一个源文件(例如文件名B)的变量,方法有2种:(1)在A文件中必须用extern声明在B文件中定义的变量(当然是全局变量);(2)在A文件中添加B文件对应的头文件,当然这个头文件包含B文件中的变量声明,也即在这个头文件中必须用extern声明该变量,否则,该变量又被定义一次。
对函数而言,如果你想在本源文件(例如文件名A)中使用另一个源文件(例如文件名B)的函数,方法有2种:(1)在A文件中用extern声明在B文件中定义的函数(其实,也可省略extern,只需在A文件中出现B文件定义函数原型即可);(2)在A文件中添加B文件对应的头文件,当然这个头文件包含B文件中的函数原型,在头文件中函数可以不用加extern。
对上述总结换一种说法:
(a)对于一个文件中调用另一个文件的全局变量,因为全局变量一般定义在原文件.c中,我们不能用#include包含源文件而只能包含头文件,所以常用的方法是用extern int a来声明外部变量。 另外一种方法是可以是在a.c文件中定义了全局变量int global_num ,可以在对应的a.h头文件中写extern int global_num ,这样其他源文件可以通过include a.h来声明她是外部变量就可以了。
(b)还有变量和函数的不同举例 int fun(); 和 extern int fun(); 都是声明(定义要有实现体)。 用 extern int fun() 只是更明确指明是声明而已。而 int a; 是定义 extern int a; 是声明。
(c)此外,extern修饰符可用于C++程序中调用c函数的规范问题。
比如在C++中调用C库函数,就需要在C++程序中用extern "C"声明要引用的函数。这是给链接器用的,告诉链接器在链接的时候用C函数规范来链接。主要原因是C++和C程序编译完成后在目标代码中命名规则不同。
C++语言在编译的时候为了解决的多态问题,会将名和参数联合起来生成一个中间的名称,而c语言则不会,因此会造成链接时找不到对应的情况,此时C就需要用extern "C"进行链接指定,这告诉编译器,请保持我的名称,不要给我生成用于链接的中间名。
参考网址:https://www.runoob.com/w3cnote/extern-head-h-different.html
const 是 constant 的缩写,本意是不变的,不易改变的意思。在 C++ 中是用来修饰内置类型变量,自定义对象,成员函数,返回值,函数参数。C++ const 允许指定一个语义约束,编译器会强制实施这个约束,允许程序员告诉编译器某值是保持不变的。如果在编程中确实有某个值保持不变,就应该明确使用const,这样可以获得编译器的帮助。(个人感觉:const主要就是设置不变量,不变的量可以是值、指针、返回值)
const int a = 7;
int b = a; // 正确
a = 8; // 错误,不能改变
a 被定义为一个常量,并且可以将 a 赋值给 b,但是不能给 a 再次赋值。对一个常量赋值是违法的事情,因为 a 被编译器认为是一个常量,其值不允许修改。
接着看如下的操作:
实例一
#include
using namespace std;
int main(void)
{
const int a = 7;
int *p = (int*)&a;
*p = 8;
cout<
对于 const 变量 a,我们取变量的地址并转换赋值给 指向 int 的指针,然后利用 *p = 8; 重新对变量 a 地址内的值赋值,然后输出查看 a 的值。
从下面的调试窗口看到 a 的值被改变为 8,但是输出的结果仍然是 7。
从结果中我们可以看到,编译器然后认为 a 的值为一开始定义的 7,所以对 const a 的操作就会产生上面的情况。所以千万不要轻易对 const 变量设法赋值,这会产生意想不到的行为。
如果不想让编译器察觉到上面到对 const 的操作,我们可以在 const 前面加上 volatile 关键字。
Volatile 关键字跟 const 对应相反,是易变的,容易改变的意思。所以不会被编译器优化,编译器也就不会改变对 a 变量的操作。
const 修饰指针变量有以下三种情况。
A: const 修饰指针指向的内容,则内容为不可变量。
B: const 修饰指针,则指针为不可变量。
C: const 修饰指针和指针指向的内容,则指针和指针指向的内容都为不可变量。
对于 A:
const int *p = 8;
则指针指向的内容 8 不可改变。简称左定值,因为 const 位于 * 号的左边。
对于 B:
int a = 8;
int* const p = &a;
*p = 9; // 正确
int b = 7;
p = &b; // 错误
对于 const 指针 p 其指向的内存地址不能够被改变,但其内容可以改变。简称,右定向。因为 const 位于 * 号的右边。
对于 C: 则是 A 和 B的合并
int a = 8;
const int * const p = &a;
这时,const p 的指向的内容和指向的内存地址都已固定,不可改变。
对于 A,B,C 三种情况,根据 const 位于 * 号的位置不同,我总结三句话便于记忆的话:"左定值,右定向,const修饰不变量"。
对于 const 修饰函数参数可以分为三种情况。
A:值传递的 const 修饰传递,一般这种情况不需要 const 修饰,因为函数会自动产生临时变量复制实参值。
实例
#include
using namespace std;
void Cpf(const int a)
{
cout<
B:当 const 参数为指针时,可以防止指针被意外篡改。
实例
#include
using namespace std;
void Cpf(int *const a)
{
cout<<*a<<" ";
*a = 9;
}
int main(void)
{
int a = 8;
Cpf(&a);
cout<
C:自定义类型的参数传递,需要临时对象复制参数,对于临时对象的构造,需要调用构造函数,比较浪费时间,因此我们采取 const 外加引用传递的方法。并且对于一般的 int、double 等内置类型,我们不采用引用的传递方式。
实例
#include
using namespace std;
class Test
{
public:
Test(){}
Test(int _m):_cm(_m){}
int get_cm()const
{
return _cm;
}
private:
int _cm;
};
void Cmf(const Test& _tt)
{
cout<<_tt.get_cm();
}
int main(void)
{
Test t(8);
Cmf(t);
system("pause");
return 0;
}
结果输出 8。
对于 const 修饰函数的返回值。
Const 修饰返回值分三种情况。
A:const 修饰内置类型的返回值,修饰与不修饰返回值作用一样。
实例
#include
using namespace std;
const int Cmf()
{
return 1;
}
int Cpf()
{
return 0;
}
int main(void)
{
int _m = Cmf();
int _n = Cpf();
cout<<_m<<" "<<_n;
system("pause");
return 0;
}
B: const 修饰自定义类型的作为返回值,此时返回的值不能作为左值使用,既不能被赋值,也不能被修改。
C: const 修饰返回的指针或者引用,是否返回一个指向 const 的指针,取决于我们想让用户干什么。
const 修饰类成员函数,其目的是防止成员函数修改被调用对象的值,如果我们不想修改一个调用对象的值,所有的成员函数都应当声明为 const 成员函数。
注意:const 关键字不能与 static 关键字同时使用,因为 static 关键字修饰静态成员函数,静态成员函数不含有 this 指针,即不能实例化,const 成员函数必须具体到某一实例。
下面的 get_cm()const; 函数用到了 const 成员函数:
实例
#include
using namespace std;
class Test
{
public:
Test(){}
Test(int _m):_cm(_m){}
int get_cm()const
{
return _cm;
}
private:
int _cm;
};
void Cmf(const Test& _tt)
{
cout<<_tt.get_cm();
}
int main(void)
{
Test t(8);
Cmf(t);
system("pause");
return 0;
}
如果 get_cm() 去掉 const 修饰,则 Cmf 传递的 const _tt 即使没有改变对象的值,编译器也认为函数会改变对象的值,所以我们尽量按照要求将所有的不需要改变对象内容的函数都作为 const 成员函数。
如果有个成员函数想修改对象中的某一个成员怎么办?这时我们可以使用 mutable 关键字修饰这个成员,mutable 的意思也是易变的,容易改变的意思,被 mutable 关键字修饰的成员可以处于不断变化中,如下面的例子。
实例
#include
using namespace std;
class Test
{
public:
Test(int _m,int _t):_cm(_m),_ct(_t){}
void Kf()const
{
++_cm; // 错误
++_ct; // 正确
}
private:
int _cm;
mutable int _ct;
};
int main(void)
{
Test t(8,7);
return 0;
}
这里我们在 Kf()const 中通过 ++_ct; 修改 _ct 的值,但是通过 ++_cm 修改 _cm 则会报错。因为 ++_cm 没有用 mutable 修饰。
参考网址:https://www.runoob.com/w3cnote/cpp-const-keyword.html