文章目录
一、const
被const修饰的变量或者函数具有“只读属性”,const可以修饰变量、作为函数参数、修饰成员函数,修饰函数返回值。
1、修饰变量
修饰变量时,变量的值不可以被修改。
const int a; // a的值不允许被改变
const int *a; // 指针a指向的内容可以改变,但是不能改变*a的值
int * const a; // 指针a指向的内容不可以改变,但是可以改变*a的值
const int * const a; // 指针a指向的内容不可以改变,也不可以改变*a的值
2、修饰函数参数
修饰函数参数时,表示不能修改参数的值或者指针所指向的内容,也可以作为重载的标志(有const只读,没有const读写都可以)。
int fun(int a);
int fun(const int &a);
3、修饰函数/成员函数
修饰成员函数时,表示不能修改类的数据成员并且不能调用非const函数。
#include <iostream>
using namespace std;
class A{
private:
int i;
public:
// set函数需要设置i的值,所以不能声明为const
void set(int n){
i = n;
}
// get函数返回i的值,不需要对i进行修改,则可以用const修饰。防止在函数体内对i进行修改
int get() const{
return i;
}
};
4、修饰函数返回参数
修饰函数返回参数时,表明函数返回值不可以被修改,即使得函数调用表达式不能作为左值。
const int fun(const int &a);
二、mutable
mutalbe的意思是“可变的,易变的”。在C++中,mutable也是为了突破const的限制而设置的。被mutable修饰的变量(mutable只能由于修饰类的非静态数据成员),将永远处于可变的状态,即使在一个const函数中。
1、mutable的应用场景
一个常用的场景是计算某个函数被调用的次数。
#include <iostream>
class Person {
public:
Person();
~Person();
int getAge() const; /*调用方法*/
int getCallingTimes() const; /*获取上面的getAge()方法被调用了多少次*/
private:
int age;
char *name;
float score;
mutable int m_nums; /*用于统计次数*/
};
Person::Person()
{
m_nums = 0;
}
Person::~Person(){}
int Person::getAge() const
{
std::cout << "Calling the method" << std::endl;
m_nums++;
// age = 4; 仍然无法修改该成员变量
return age;
}
int Person::getCallingTimes()const
{
return m_nums;
}
int main()
{
Person *person = new Person();
for (int i = 0; i < 10; i++) {
person->getAge();
}
std::cout << "getAge()方法被调用了" << person->getCallingTimes() << "次" << std::endl;
delete person;
getchar();
return 0;
}
三、static
1、限制变量的作用域(隐藏)
没有加static关键字的变量和函数具有全局可见性,加了static后,就会对其他源文件隐藏。
2、保持变量内容的持久性
被static修饰的变量在程序刚开始运行时就会初始化,并存储在静态存储区(还有全局变量),生存期为整个源程序。
#include <stdio.h>
int fun()
{
// 在第一次进入这个函数的时候,变量a被初始化为10!并接着自减1,以后每次进入该函数就
// 不会被再次初始化了,仅进行自减1的操作;在static发明前,要达到同样的功能,
// 则只能使用全局变量:
static int count = 10;
return count--;
}
int main(void)
{
printf("global\t\tlocal static\n");
for(; count <= 10; ++count)
printf("%d\t\t%d\n", count, fun());
return 0;
}
3、默认初始化为0
其实全局变量也具备这一属性,因为全局变量也存储在静态数据区。在静态数据区,内存中所有的字节默认值都是0x00,某些时候这一特点可以减少程序员的工作量。
#include <stdio.h>
int a;
int main()
{
int i;
static char str[10];
printf("integer: %d; string: (begin)%s(end)", a, str);
return 0;
}
4、C++中的类成员声明static
在类中声明static变量或者函数时,初始化时使用作用域运算符来标明它所属类,因此,静态数据成员是类的成员,而不是对象的成员,这样就出现以下作用:
-
类的静态成员函数是属于整个类而非类的对象,所以它没有this指针,这就导致它仅能访问类的静态数据和静态成员函数。
-
不能将静态成员函数定义为虚函数。
-
静态数据成员是静态存储的,所以必须对它进行初始化(在类外进行),<数据类型><类名>::<静态数据成员名>=<值>。
-
静态成员函数可以通过类名::静态成员函数的方式进行访问,不需要实例化。
四、extern
extern "C"的作用是在C++编译器中调用被C语言编译器编译过的函数。
为什么要这么做呢?原因是C++中支持函数重载但是C语言中不支持,而且C++的编译器会对程序中符号进行修饰。
/* file: test_extern_c.h */
#ifndef __TEST_EXTERN_C_H__
#define __TEST_EXTERN_C_H__
#ifdef __cplusplus
extern "C" {
#endif
extern int ThisIsTest(int a, int b);
#ifdef __cplusplus
}
#endif
#endif
/* test_extern_c.c */
#include "test_extern_c.h"
int ThisIsTest(int a, int b)
{
return (a + b);
}
/* main.cpp */
#include "test_extern_c.h"
#include <stdio.h>
#include <stdlib.h>
class FOO {
public:
void bar(int a, int b)
{
printf("result=%i/n", ThisIsTest(a, b));
}
};
int main(int argc, char **argv)
{
int a = atoi(argv[1]);
int b = atoi(argv[2]);
FOO *foo = new FOO();
foo->bar(a, b);
return(0);
}
// 编译:
gcc -c test_extern_c.c
g++ main.cpp test_extern_c.o -o main
./main 1 2
五、volatile
volatile的作用: 作为指令关键字,确保本条指令不会因编译器的优化而省略,且要求每次直接读值。简单地说就是防止编译器对代码进行优化。当要求使用volatile 声明的变量的值的时候,系统总是重新从它所在的内存读取数据,而不是使用保存在寄存器中的备份。即使它前面的指令刚刚从该处读取过数据。而且读取的数据立刻被保存。
volatile跟多线程无关,它不是一种同步手段,用它来实现线程安全是错的。原子和锁会保证线程安全性。
-
易变性:两条语句,下一条语句不会直接使用上一条语句对应的volatile变量的寄存器内容,而是重新从内存中读取。
-
不可优化:volatile告诉编译器,不要对我这个变量进行各种激进的优化,甚至将变量直接消除,保证程序员写在代码中的指令,一定会被执行。
-
顺序性:保证volatile变量间的顺序性,编译器不会进行乱序优化。
1、面试题—>一个参数可以即是const又是volatile吗?
可以,一个例子是只读状态寄存器。
六、inline
使用inline声明的函数叫做内联函数,原理是将函数调用替换为函数实体(编译期),不需要中断调用,减少了函数参数压栈过程,增加了函数调用的效率。但是有几点需要注意:
-
内联函数只适合体积较小(一般十行以内),没有循环、递归和开关操作的函数;
-
在类内定义的成员函数都是内联函数;
-
过多使用内联函数可能会增加程序体积,减少高速缓存命中率。
1、面试题—>C++中哪些函数不能声明为inline?
-
包含了递归、循环等结构的函数一般不会被内联。
-
虚函数一般不会内联,但是如果编译器能在编译时确定具体的调用函数,那么仍然会就地展开该函数。
-
如果通过函数指针调用内联函数,那么该函数将不会内联而是通过call进行调用。
-
构造和析构函数一般会生成大量代码,因此一般也不适合内联。
-
如果内联函数调用了其他函数也不会被内联。
如果想要阻止某函数被内联,可以在函数体前加上 __attribute__((noinline))
。
七、explicit
关键字explicit只对一个实参的构造函数有效,只能在类内定义,它表示禁止对构造函数进行隐式转换。当我们用explicit关键字声明构造函数时,它将只能以直接初始化的形式使用,而不能使用赋值初始化。
explicit A(const string &s):a(s) {};
A a("abc");//直接初始化。
a.fun(static_cast<A>(cin));//强制执行显式转换。
八、#define
#define表示宏定义,在预处理阶段,进行简单的文本替换,没有类型检查,没有分配内存空间,不能调试(这是一个缺点,所以建议尽可能使用const替换#define)。
- #define不能以分号结束,尽量使用括号;
- 宏定义不是函数!。
// 比较两个数的较小者
#define MIN(a, b) ( (a) <= (b) ? (a) : (b) )
// 返回一年有多少天
#define SECONDS_PER_YEAR (365 * 24 * 60 * 60)UL
1、面试题—>内联函数和宏定义的区别?
-
宏定义不是函数,只是在预处理阶段进行简单的文本替换;而内联函数是在编译期将函数调用替换为函数实体。
-
宏定义没有参数类型检查,而内联函数会进行检查。
九、typedef
typedef表示为参数定义一个别名,具有一定的封装性。
typedef (int*) pInt1;
#define pInt2 int*
pInt1 a, b; // a和b都是int类型的指针
pInt2 a, b; // a是int类型的指针,但是b只是一个int变量
十、sizeof
sizeof返回的是变量所占用的字节数,是运算符而不是函数,常见的sizeof如下所示:
// 基本数据类型的sizeof
cout<<sizeof(char)<<endl; //结果是1
cout<<sizeof(int)<<endl; //结果是4
cout<<sizeof(unsigned int)<<endl; //结果是4
cout<<sizeof(long int)<<endl; //结果是4
cout<<sizeof(short int)<<endl; //结果是2
cout<<sizeof(float)<<endl; //结果是4
cout<<sizeof(double)<<endl; //结果是8
// 指针变量的sizeof
char *pc ="abc";
sizeof(pc); // 结果为4
sizeof(*pc); // 结果为1
int *pi;
sizeof(pi); //结果为4
sizeof(*pi); //结果为4
// 数组的sizeof值等于数组所占用的内存字节数
char a1[] = "abc";
int a2[3];
sizeof( a1 ); // 结果为4,字符 末尾还存在一个NULL终止符
sizeof( a2 ); // 结果为3*4=12(依赖于int)
void foo3(char a3[3]) // 数组作为函数参数时就变成指针
{
int c3 = sizeof( a3 ); // c3 == 4
}
void foo4(char a4[])
{
int c4 = sizeof( a4 ); // c4 == 4
}
// 空结构体的sizeof
// C++编译器会在空类或空结构体中增加一个虚设的字节(有的编译器可能不止一个),
// 以确保不同的对象都具有不同的地址。
struct S5 { };
sizeof( S5 ); // 结果为1
// 类的sizeof
class A
{
public:
int b;
float c;
char d;
}; // 内存对齐
int main(void)
{
A object;
cout << "sizeof(object) is " << sizeof(object) << endl;
return 0 ;
}
1、面试题—>说明sizeof的使用场合?
-
查看某个对象所占用的字节数。
-
与存储分配和I/O系统那样的例程进行通信。
2、面试题—>说明sizeof和strlen?
-
sizeof是运算符而strlen是函数。
-
strlen的结果是在运行期计算出来的,结果是字符串的长度而不是所占用的内存大小。
-
strlen的参数只能是char*,而且必须是以’\0’结尾的。
十一、union
1、union和struct的区别?
-
结构体:每个成员都拥有自己的内存,互不干涉,同时遵循内存对齐原则。一个struct变量的总大小等于所有成员的大小之和。
-
联合体:各成员共用一块内存空间,并且同时只有一个成员可以得到这块内存的使用权(对该内存的读写),各变量共用一个内存首地址。
2、union的赋值机制
#include <stdio.h>
union u{
int a;
short b;
};
int main()
{
union u t;
t.a=100000;
t.b=10;
printf("%d %hd", t.a, t.b); // 输出结果为: 65546 10。
return 0;
}
给联合体中的成员赋值时,只会对这个成员所属的数据类型所占内存空间的大小覆盖成后来的这个值,而不会影响其他位置的值。
成员a为int类型,b为short类型,第一次给a赋值100000,二进制为0000 0000 0000 0001 1000 0110 1010 0000
,第二次给b赋值,由于short只占2个字节,所以只会覆盖16位二进制,0000 0000 0000 1010
,最后的结果是0000 0000 0000 0001 0000 0000 0000 1010
(从低位覆盖),所以输出结果为65546。