一:const的那些秘密
1.1 const含义
使用const修饰符的类型称作为:常类型。(常类型的变量或者对象都是不能被更新的)
1.2 const作用
(1)定义常量
const int a = 100;
a = 0; // 错误:此时a为常量,不可更改
const string s = "helloworld";
const int i, j =0; //错误: 因为i没有进行初始化
所以:第一:常量被定义后,不可以修改;第二:必须进行初始化,因为常量在定义后就不可以被修改,所以定义时必须初始化。
(2)防止参数修改,其程序保护作用
void f(const int i)
{
i++; // 错误写法,此时的 i 是不能够被重新更新的
}
1.3 const对象默认为文件局部变量
注意:非const变量,都是默认为了extern。所以,如果想一个文件中的const变量在其他文件中被访问调用,就在文件中显示的指定它为extern。
如下:我们在宁一个文件访问未被const修饰的变量
## file1.cpp //代表宁一个cpp文件代码,后续操作都如此
int ext ;
## file2.cpp
#include<iostream>
extern int ext;
int main()
{
std::cout<<(ext+10)<<std::endl;
}
我们在不同文件访问const常量
## file1.cpp
extern const int ext = 10;
## file2.cpp
#include<iostream>
extern const int ext;
int main()
{
std::cout<<(ext+10)<<std::endl;
}
小结:对比两种方式对其他文件变量的访问,可以清楚了解到,未被const修饰的变量不需要添加任何extern进行显示声明!!!而const变量我们不仅添加了extern进行显示声明,我们对它做了初始化(再强调一次,常量再定义后不可以被修改,所以定义时必须初始化)。
1.4 指针与const
这个问题属于面试里面最常考的问题,也是最容易记混淆的问题。
与指针相关的const有以下四种类型:
const char *a ; //指const对象的指针,或者(指向常量的指针)
char const *a; //同上
char *const a; //指向类型对象的const指针,或者(常指针、const指针)
const char *const a; //指向const对象的const指针
小结:如果:const位于 * 的左侧(const * ),那么const就是用来修饰指针所指的变量,即指针指向为常量。如果:const位于 * 的右侧( * const),那么const就是用来修饰指针本身,即指针本身是常量。
具体分析和如何使用教程如下:
(1)指向常量的指针
const int *ptr;
*ptr = 1 ; // 错误
ptr是一个指向int类型的const对象的指针,const定义为int类型,也就是ptr所指向的对象类型,而不是ptr本身,所以在我们再定义的时候,ptr可以不用赋值初始值。但是我们不能通过ptr去修改所指对象的值。
除此之外,也不能用void * 指针保存const对象的地址,必须使用const void * 类型的指针保存const对象的地址。
const int p = 10;
const void *p = &p;
void *p = &p; //错误
重点:可以把非const对象的地址赋给指向const对象的指针。
const int *ptr;
int val = 3;
ptr = &val; //正确
注意:我们不能够通过ptr指针来修改val的值,即使它指向的是非const对象。
(2)常指针
const指针必须进行初始化,并且const指针的值不能修改
int num = 0;
int * const ptr = # // const指针必须初始化,并且const指针的值不能修改
int *t = #
*t = 1;
cout<<*ptr<endl; //值为 1 ;
我们可以看到,我们是通过间接形式,通过非const指针修改。
如果,当我们把一个常量地址赋值给ptr的时候,因为我们知道ptr指向的是一个变量,而不是const常量,所以会出现以下报错:
#include<iostream>
using namespace std;
int main(){
const int a =0;
int * const ptr = &a; // 错误:报错为:const int * -> int *
cout<<*ptr<<endl;
}
错误表明为一个变量指向的是地址,那如何解决呢?可以用上面改一个方法,去掉const,也可以用剩下没讲的一个类型,我们修改为 const int * const ptr。
(3)指向常量的常指针
const int p = 3;
const int * const ptr = &p;
此处,ptr是一个const指针,然后是指向一个int类型的const对象。
1.5 const在函数中的使用
1.5.1修饰函数返回值
这个和前面讲的修饰普通变量和指针的含义基本没变化:
(1)const int
const int func1():
这个本身就没什么意义,因为参数的返回本身就是赋值给其他变量的。
(2)const int *
const int * func1();
指针指向的内容是不变的
(3)int *const
int *const func2();
指针本身是补课变的
1.5.2修饰函数参数
(1)传递过来的参数或指针本身在函数内就不可变,无意义
void func(const int var); //传递的参数本来就不可变
void func(int *const var); // 指针本身就不可变
用处表明,参数在函数体内,不能被修改,但是这里没任何意义啊,因为var就是形参,在函数内不会改变,传入的形参指针也是一样的。
(2)参数指针所指内容为常量不可变
void func(char *dst,const char *src);
src为输入参数,dst为输出参数。在对src加上const修饰后,如果函数体内的语句试图修改src的内容,则编译器出错(注意对比,这个和上面那个不一样,const在 * 左右侧不同)。
(3)参数为引用,增加运行效率,也防止被修改
void func(const A &a); // A 为类型
增加效率原因:如果,我们只是单纯的 void func( A a);,那么,在函数体内,我们将产生A 类型的临时对象用来复制参数a,对临时对象的构造、复制、析构都消耗时间。
所以进一步升级 void func(A &a),这样属于“引用传递”,不需要产生临时对象,但是可能会改变a,所以最终我们添加const进行修饰。
1.6 const在类中的使用
在一个类中,对不会修改数据成员的函数,最好都声明为const类型,提高从程序的健壮性。
对类中的const成员变量,必须通过初始化列表进行初始化操作:
class Apple
{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
};
Apple::Apple(int i):apple_number(i)
{
}
const对象只能访问const成员函数,而非const对象可以访问任意成员函数,包括const成员函数:
//apple.cpp
class Apple
{
private:
int people[100];
public:
Apple(int i);
const int apple_number;
void take(int num) const;
int add(int num);
int add(int num) const;
int getCount() const;
};
//main.cpp
#include<iostream>
#include"apple.cpp"
using namespace std;
Apple::Apple(int i):apple_number(i)
{
}
int Apple::add(int num){
take(num);
}
int Apple::add(int num) const{
take(num);
}
void Apple::take(int num) const
{
cout<<"take func "<<num<<endl;
}
int Apple::getCount() const
{
take(1);
// add(); //error
return apple_number;
}
int main(){
Apple a(2);
cout<<a.getCount()<<endl;
a.add(10);
const Apple b(3);
b.add(100);
return 0;
}
//编译: g++ -o main main.cpp apple.cpp
//结果
take func 1
2
take func 10
take func 100
上文我们提到,对const常量使用初始化列表方式进行的初始化,我们也可以用以下的方法:
(1)将常量定义与static结合:
static const int apple_number
(2)在外面进行初始化:
const int Apple:applr_number = 10;
二:extern的那些秘密
该关键词,主要是对c++和c编译之间的使用。
2.1 c++和c编译区别
c++中,在头文件可以进场看到 extern “c” 修饰函数,意义是什么?用来c++连接在c语言模块中定义的函数。
如果我们在c++中使用c语言实现的函数,不添加 extern则会报。
2.2 c++调用c函数
c++调用c函数:引用c的头文件,需要加上:extern “c”
//add.h
#ifndef ADD_H
#define ADD_H
int add(int x,int y);
#endif
//add.c
#include "add.h"
int add(int x,int y) {
return x+y;
}
//add.cpp
#include <iostream>
using namespace std;
extern "C" {
#include "add.h"
}
int main() {
add(2,3);
return 0;
}
2.3 c中调用c++函数
extern “c” 在c语言中语法是错误的,需要放在c++的头文件中:
// add.h
#ifndef ADD_H
#define ADD_H
extern "C" {
int add(int x,int y);
}
#endif
// add.cpp
#include "add.h"
int add(int x,int y) {
return x+y;
}
// add.c
extern int add(int x,int y);
int main() {
add(2,3);
return 0;
}
小结:
(1)c++调用c函数
//xx.h
extern int add(...)
//xx.c
int add(){
}
//xx.cpp
extern "C" {
#include "xx.h"
}
(2)c调用c++函数
//xx.h
extern "C"{
int add();
}
//xx.cpp
int add(){
}
//xx.c
extern int add();
三:static的那些秘密
static当与不同类型一起使用时,static关键字就含有不同的含义。
3.1 静态变量
函数中的变量,类中的变量
3.1.1函数中的静态变量
当变量声明为static时,空间将在程序的声明周期内分配,即使多次调用该函数,静态变量的空间也只会分配一次,前一次调用中的变量值通过下一次函数调用传递。
#include <iostream>
#include <string>
using namespace std;
void demo()
{
// static variable
static int count = 0;
cout << count << " ";
// value is updated and
// will be carried to next
// function calls
count++;
}
int main()
{
for (int i=0; i<5; i++)
demo();
return 0;
}
//输出
0 1 2 3 4
count被声明为static,所以,它的值通过函数调用来传递,每次调用函数,都不会对变量计数进行初始化。
3.1.2 类中的静态变量
声明为static的变量只被初始化一次,因为它们在单独的静态存储中分配了空间,因此类中的静态变量由对象共享。对于不同的对象,不能有相同静态变量的多个副本。也是因为这个原因,静态变量不能使用构造函数初始化。
#include<iostream>
using namespace std;
class Apple
{
public:
static int i;
Apple()
{
// Do nothing
};
};
int main()
{
Apple obj1;
Apple obj2;
obj1.i =2;
obj2.i = 3;
// prints value of i
cout << obj1.i<<" "<<obj2.i;
}
程序中看到我们已经尝试为多个对象创建静态变量i的多个副本。但是程序都没法运行。
修改如下:
#include<iostream>
using namespace std;
class Apple
{
public:
static int i;
Apple()
{
// Do nothing
};
};
int Apple::i = 1;
int main()
{
Apple obj;
// prints value of i
cout << obj.i;
}
// 输出
1
3.2静态成员
3.2.1类对象为静态
就像变量一样,对象也在声明为static时具有范围,直到程序的生命周期。
考虑以下程序,其中对象是非静态的:
#include<iostream>
using namespace std;
class Apple
{
int i;
public:
Apple()
{
i = 0;
cout << "Inside Constructor\n";
}
~Apple()
{
cout << "Inside Destructor\n";
}
};
int main()
{
int x = 0;
if (x==0)
{
Apple obj;
}
cout << "End of main\n";
}
//输出
Inside Constructor
Inside Destructor
End of main
在上面的程序中,对象在if块内声明为非静态。因此,变量的范围仅在if块内。因此,当创建对象时,将调用构造函数,并且在if块的控制权越过析构函数的同时调用,因为对象的范围仅在声明它的if块内。
如果我们将对象声明为静态,现在让我们看看输出的变化:
#include<iostream>
using namespace std;
class Apple
{
int i;
public:
Apple()
{
i = 0;
cout << "Inside Constructor\n";
}
~Apple()
{
cout << "Inside Destructor\n";
}
};
int main()
{
int x = 0;
if (x==0)
{
static Apple obj;
}
cout << "End of main\n";
}
//输出
Inside Constructor
End of main
Inside Destructor
在main结束后调用析构函数。这是因为静态对象的范围是贯穿程序的生命周期。
3.2.2 类中的静态函数
就像类中的静态数据成员或静态变量一样,静态成员函数也不依赖于类的对象。我们被允许使用对象和’.'来调用静态成员函数。但建议使用类名和范围解析运算符调用静态成员。
允许静态成员函数仅访问静态数据成员或其他静态成员函数,它们无法访问类的非静态数据成员或成员函数。
#include<iostream>
using namespace std;
class Apple
{
public:
// static member function
static void printMsg()
{
cout<<"Welcome to Apple!";
}
};
// main function
int main()
{
// invoking a static member function
Apple::printMsg();
}
//输出
Welcome to Apple!