c++关键词(你不了解的秘密)(c++基础进阶知识点)

本文详细探讨了C++中的const、extern和static的关键概念,包括常量定义、作用、指针与const的关系,以及它们在函数参数、类成员和范围中的应用。同时揭示了extern在C++和C编译中的差异以及static变量和静态成员的特性。
摘要由CSDN通过智能技术生成

一: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 = &num;  // const指针必须初始化,并且const指针的值不能修改
int *t = &num;
*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 = 3const 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!

四:this指针的那些秘密

  • 20
    点赞
  • 16
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值