精华内容
下载资源
问答
  • 静态数据成员和静态成员函数

    万次阅读 多人点赞 2018-08-26 19:18:35
    c++中的static静态数据成员和静态成员函数应该是让大家比较头疼的东西,好像也是找工作公司面试中常常问到的东西。我自己也深有体会,在学习c++的过程中,总感觉static很烦人,但是又是一个必须懂的东西,所以今天就...

    转载自:https://blog.csdn.net/computer_liuyun/article/details/29235111

    c++中的static静态数据成员和静态成员函数应该是让大家比较头疼的东西,好像也是找工作公司面试中常常问到的东西。我自己也深有体会,在学习c++的过程中,总感觉static很烦人,但是又是一个必须懂的东西,所以今天就对静态数据成员和静态成员函数坐下小结哈!

    一、静态数据成员

    1.静态数据成员怎么去定义?

    在类中声明静态数据成员很简单,是以static关键字表明即可,如下所示

    class Test{
    private:
    	//静态数据成员
    	static int a;
    };
    

    a就是静态数据成员了,在类中只能声明可是不能定义哈!

    要对静态数据成员定义和初始化必须在类的外面也就是在全局作用域中定义,如果定义不给出初值,则默认初值为0

    class Test{
    public:
    	int GetA() const{return a;}
    private:
    	//静态数据成员
    	static int a;
    };
    //int Test::a;如果这样定义不赋予初值则初值为零
    int Test::a = 1;
    #include <iostream>
    int main()
    {
    	Test T;
    	std::cout << T.GetA() << std::endl;
    }
    

    定义时一定要在全局作用域中定义,一定不要在类中定义!

    静态数据成员甚至在类没有任何对象的时候都可以访问,静态成员可以独立访问,无需依赖任何对象的建立

    另外,不要试图在头文件中定义(初始化)静态数据成员。在大多数的情况下,这样做会引起重复定义这样的错误。即使加上#ifndef #define #endif或者#pragma once也不行。

     

    2.静态数据成员被类的所有对象共享,包括该类的派生类对象,基类对象和派生类对象共享基类的静态数据成员

    答:静态数据成员不属于任何对象,类的静态数据成员的存在不依赖于任何类对象的存在,静态数据成员是由类的所有对象共享的。例子如下:

    class Base{
    public:
    	//静态数据成员
    	static int a;
    };
    class Derived : public Base{
     
    };
    //int Test::a;如果这样定义不赋予初值则初值为零
    int Base::a;
    #include <iostream>
    int main()
    {
    	Base B;
    	Derived D;
    	B.a++;
    	std::cout << B.a << std::endl;
    	D.a++;
    	std::cout << D.a << std::endl;
    	
    }
    

    打印结果如下:

    由打印结果看出来,派生类对象和基类对象都是共享基类的静态数据成员,而基类的所有对象也是共享该静态数据成员,且该静态数据成员应该在全局作用域中定义,可以赋予初值也可以不赋予初值,如果不赋予初值,静态数据成员有其默认值。

     

    3.静态数据成员可以作为成员函数的默认形参,而普通数据成员则不可以

    答:不多说,直接看例子马上就明白了哈!

    class Test{
    public:
    	//静态数据成员
    	static int a;
    	int b;
    	void fun_1(int i = a);//正确
    	void fun_2(int i = b);//报错
    };
    

    4.静态数据成员的类型可以是所属类的类型,而普通数据成员则不可以。普通数据成员的只能声明为 所属类类型的 指针或引用

    答:这个也不多说,直接看例子就可以懂什么意思哈!

    class Test{
    public:
    	//静态数据成员
    	static Test a;//正确
    	Test b;//报错
    	Test *pTest;//正确
    	Test &m_Test;//正确
    	static Test *pStaticObject;//正确
    };
    

    5.静态数据成员在const函数中可以修改,而普通的数据成员是万万不能修改的哈!

    答:看个例子

    class Test{
    public:
    	Test():b(0){}
    	//静态数据成员
    	static int a;//正确
    	int b;
    	void test() const
    	{
    		a++;
    		b++;//const指的不能修改当前调用该函数对象的数据成员
    	}
    };
    int Test::a;
    

    const修饰的时当前this指针所指向的对象是const,但是静态数据成员不属于任何类的对象,它被类的所有对象修改,所以this指针不修饰静态的数据成员,所以可以更改。

     

    二、静态成员函数

    静态成员函数的声明也很简单,就是在类的成员函数前加上static关键字即可,和静态成员一样,静态成员函数也是属于类的,它并不属于任何对象,当调用静态成员函数时应该使用类名和域运算符“∷”,当然也可以使用对象调用操作,但是这样的操作并不意味着静态成员函数属于这个对象,它只是被这个对象共享而已,这样也就决定了静态成员函数中是不能访问本类中的非静态数据成员的,它是不能访问非静态数据成员的,在c++中静态成员函数主要用来访问静态数据成员而不访问非静态数据成员

    1.静态成员函数不能调用非静态成员函数,但是反过来是可以的

    2.静态成员函数没有this指针,也就是说静态成员函数不能使用修饰符(也就是函数后面的const关键字)

    3.静态成员函数的地址可用普通函数指针储存,而普通成员函数地址需要用 类成员函数指针来储存。

    总结:其实声明为静态,不论是静态数据成员还是静态成员函数,它们都是不依赖于对象而存在的,类在定义后并不分配存储空间,而是在定义类的对象的时候才分配存储空间,相反静态的数据成员和静态的成员函数是已经在内存中开辟了内存空间了,所以静态数据成员可以独立的访问在任何类对象没有建立起来都可以访问,并且静态成员函数不可以调用非静态成员函数,因为非静态成员函数只有在类对象建立以后才可以调用,相反则是可以的。我觉得当对某个判断产生怀疑的时候应该去测试,只有验证了才知道是不是对的哈!

    为了能更好阐释静态数据成员和静态成员函数,然后在网上搜了博客,感觉有些例子不错(因找不到最初的出处,所以无法注明请原作者谅解),所以就拿来给大家参考一下哈!

    关于玩篮球的例子很能明显解释静态数据成员和静态成员函数到底是怎么回事

    你们班里面有10个人(10个比如高一一班的对象),体育老师分给你们一个篮球(静态成员函数),你们每个人都带了一个篮球(非静态成员数),你们都很小气,自己的球只能自己拍,要是5对5打比赛,那就只能用那个静态的篮球了(每个人都可以拿来用,但是带来的影响是对全体的)。因此,我可以说那个篮球是高一一班的成员。所以也就是说:静态成员函数是类的成员函数(因为高一二班就不能拿来玩),但是这个篮球最后还是要还给老师的,任何私人不得占有。希望这样你能明白,其实在内存空间里面说白了静态的成员的内存是唯一的一份,就是当你在类外声明他时开辟的,但是非静态函数的空间分配是在你实例化对象时创建的。

    为了使大家更好的理解这两个概念,还是老样子用代码来说明上面文字说明的这一切哈!

    关于学生类的例子

    //定义Student类
    #include <iostream>
    class Student                  
    {
    public:
    //定义构造函数
    Student(int n,int a,float s):num(n),age(a),score(s){ }      
    void total();
    //声明静态成员函数
    static float average();      
    private:
    	int num;
    	int age;
    	float score;
    	//静态数据成员,累计学生的总分
    	static float sum; 
    	//静态数据成员,累计学生的人数
    	static int count;           
    };
    //在全局作用域对静态数据成员初始化,如果不赋予初值,则使用其默认值零
    float Student::sum;                     
    int Student::count;
    //定义非静态成员函数
    void Student::total()                    
    {
    	//累加总分
    	sum+=score;
    	//累计已统计的人数
    	count++;                               
    }
    //定义静态成员函数
    float  Student::average()                  
    {
    	return(sum/count);
    }
    int main()
    {
    	Student stud[3]={    
    	//定义对象数组并初始化
    	Student(1001,18,70),
    	Student(1002,19,78),
    	Student(1005,20,98)
    };
    int n;
    std::cout<<"please input the number of students: ";
     //输入需要求前面多少名学生的平均成绩
    std::cin>>n;                              
    //调用3次total函数
    for(int i=0;i<n;i++)
    {
    	stud[i].total();
    }
    //调用静态成员函数
    std::cout<<"the average score of "<<n<<" students is "<<Student::average( )<<std::endl;
    return 0;
    }
    

    打印输出如下:

     

    对上面的代码做以下说明:

    首先,在主函数中定义了对象数组,存放每个学生的编号、年龄和成绩,其中sum和count是要累计所有学生的总成绩和总的学生人数,我们定义成了静态数据成员,在学生类的成员函数中,我们定义了普通的total成员函数,用来计算所有学生的总成绩和总的学生人数,另外,定义了静态成员函数average,学生类的设计大概如此

    在全局作用域对类中静态数据成员进行了定义,但未赋予初值,这意味着我们采用其默认值。

    在类的普通成员函数toal中可以引用静态数据成员sum和count,可见类的所有成员函数都可以引用类的静态数据成员。

    在类的静态成员函数average中,只能引用类的静态数据成员,不能引用非静态数据成员。

    在主函数中我们调用类的非静态成员函数时只能通过类对象来调用,如stu[i].total,但是对于静态成员函数可以直接通过类名+作用域符号来调用,如

    Student::average。

     

    展开全文
  • C++类成员函数指针使用介绍

    万次阅读 多人点赞 2019-09-21 16:07:49
    在之前写过的博客中有介绍过函数指针和指针函数的区别和简单用法(文章在这里),当时的Demo非常简单,都是C语言的写法,但是当在C++中直接像C那样使用类成员函数指针时就会报错:reference to non-static member ...

    前言

    在之前写过的博客中有介绍过函数指针和指针函数的区别和简单用法(文章在这里),当时的Demo非常简单,都是C语言的写法,但是当在C++中直接像C那样使用类成员函数指针时就会报错:reference to non-static member function must be called
    所以如果是C++中的成员函数指针其使用方法是有区别的,这里针对不同的场景做个补充说明。

    类成员函数的指针(非静态)

    指向类成员函数的指针与普通函数指针的区别在于,前者需要匹配函数的参数类型和个数以及返回值类型,还要匹配该函数指针所属的类类型。

    这是因为非静态的成员函数必须被绑定到一个类的对象或者指针上,才能得到被调用对象的this指针,然后才能调用指针所指的成员函数(所有类的对象都有自己数据成员的拷贝,但是成员函数都是共用的,为了区分是谁调用了成员函数,就必须有this指针,this指针是隐式的添加到函数参数列表里去的)。

    所以,对于类成员函数的指针使用包含以下几个步骤:

    声明: 指向类的成员函数的指针需要在指针前面加上类的类型,格式为:

    typedef 返回值 (类名::*指针类型名)(参数列表);
    

    赋值: 需要用类的成员函数地址赋值,格式为:

    指针类型名  指针名 = &类名::成员函数名;
    

    注意:这里的这个&符号是比较重要的:不加&,编译器会认为是在这里调用成员函数,所以需要给出参数列表,否则会报错;加了&,才认为是要获取函数指针。这是C++专门做了区别对待。

    调用: 针对调用的对象是对象还是指针,分别用.*和->*进行调用,格式为:

    (类对象.*指针名)(参数列表);
    
    (类指针->*指针名)(参数列表);
    

    注意:这里的前面一对括号是很重要的,因为()的优先级高于成员操作符指针的优先级。

    直接来看一个示例吧:

    class Calculation
    {
    public:
        int add(int a,int b){ //非静态函数
            return  a + b;
        }
    };
    
    typedef int (Calculation::*FuncCal)(int,int);
    
    int main()
    {
        FuncCal funAdd = &Calculation::add;
        Calculation * calPtr = new Calculation;
        int ret = (calPtr->*funAdd)(1,2);  //通过指针调用
    
        Calculation cal;
        int ret2 = (cal.*funAdd)(3,4);  //通过对象调用
    
        cout << "ret = " << ret << endl;
        cout << "ret2 = " << ret2 << endl;
        return 0;
    }
    

    指向类的静态函数的指针

    类的静态成员函数和普通函数的函数指针的区别在于,他们是不依赖于具体对象的,所有实例化的对象都共享同一个静态成员,所以静态成员也没有this指针的概念。

    所以,指向类的静态成员的指针就是普通的指针。

    class Calculation
    {
    public:
        static int add(int a,int b){ //非静态函数
            return  a + b;
        }
    };
    
    typedef int (*FuncCal)(int,int);
    
    int main()
    {
        FuncCal funAdd = &Calculation::add;
        int ret = (*funAdd)(1,2);  //直接引用
        int ret2 = funAdd(3,4);  //直接引用
    
        cout << "ret = " << ret << endl;
        cout << "ret2 = " << ret2 << endl;
        return 0;
    }
    
    

    总结以上两种情况的区别:

    • 如果是类的静态成员函数,那么使用函数指针和普通函数指针没区别,使用方法一样
    • 如果是类的非静态成员函数,那么使用函数指针需要加一个类限制一下。

    使用函数指针,很多情况下是用在函数的参数中,在一些复杂的计算,如果需要重复调用,并且每次调用的函数不一样,那么这时候使用函数指针就很方便了,可以减少代码量。

    参考资料:
    https://blog.csdn.net/houzijushi/article/details/81503409
    https://www.cnblogs.com/lvchaoshun/p/7806248.html
    https://www.cnblogs.com/AnnieKim/archive/2011/12/04/2275589.html

    展开全文
  • 1.通过类名调用静态成员函数和非静态成员函数 2.通过类的对象调用静态成员函数和非静态成员函数 3.在类的静态成员函数中使用类的非静态成员 4.在类的非静态成员函数中使用类的静态成员 5.使用类的静态成员变量 ...

    目录

            笔试题

            静态成员函数的使用规则

            虚函数的工作原理

            有关虚函数注意事项


    前言

    最近秋招,学长们都在答题,我也稍微关注了一点动向。先不说编程题,就前面的选择题有时候就能检验一个人的知识或者扩展知识是否达标。大家在学习的时候可能会认真的看完一本书,但企业的笔试题不仅有书上的知识,还有一些延伸或者说是比较偏的知识。这需要我们自己动手检验,才能对某个知识点牢固的掌握。本篇先解决两道 C++ 选择题,因为题中涉及到虚函数相关知识。在解答完选择题之后会复习虚函数实现的原理。其实类似的题我在牛客网上也见过,当时不以为然,认为这种题完全没有必要,现在回想起来感觉自己太天真了。如果真的是自己遇到了怎么办?唯一的办法就是打好基础,不要轻视或忽略任何一个知识点。

     

    笔试题

    第一题:关于C++,以下说法正确的是()

    A.构造函数可以声明为虚函数

    B.纯虚的析构函数可以不用实现

    C.静态成员函数的多态也是通过声明为虚函数来实现

    D.一个类成员函数无法同时声明为模板函数和虚函数

    第二题(选择题):
    class Test
    {
    public:
        Test(){}
        virtual ~Test(){}
        void print()
        {
            cout << "Test" <<endl;
        }
    private:
        int i;
        char array[3];
        char* c;
    };
    
    
    int main()
    {
        int isize = sizeof(class Test);
        cout << "isize:" << isize <<endl;
        return 0;
    }

     

    先从第一题开始,A选项明显是错的,构造函数不能是虚函数,具体理由在后面会总结出来。我们可以尝试将构造函数声明为虚函数。会有以下结果:

    结论:构造函数不能被声明为虚函数。

     

    以下代码可以验证B选项是否正确:

    结论:在不需要实例化Test类对象的情况下,程序不会报错,表明纯虚的析构可以不用实现。

     

    C选项的情况我也敲了代码,有以下这几种情况。情况一,声明一个静态成员函数为虚函数:

    #include <iostream>
    #include<cstdio>
    using namespace std;
    
    class Test
    {
    public:
        Test(int a){ m = a;}
        ~Test();
        virtual void print(int a);
        static void output(){ cout << "Static output" <<endl;}
        virtual static void output(int a);
    private:
        int m;
        char a[3];
        char *asd;
    };
    
    void Test::print(int a)
    {
        cout << "print" <<endl;
    }
    
    Test::~Test()
    {
        cout << "~Test" <<endl;
    }
    
    void Test::output(int a)
    {
        cout << "Virtual output" <<endl;
    }
    
    int main()
    {
        Test asd(1);
        asd.output();
        return 0;
    }

    结论:函数不能同时声明为静态和虚函数。

     

    情况二,声明一个与静态成员函数同名的虚函数:

    #include <iostream>
    #include<cstdio>
    using namespace std;
    
    class Test
    {
    public:
        Test(int a){ m = a;}
        ~Test();
        virtual void print(int a);
        static void output(){ cout << "Static output" <<endl;}
        virtual void output(int a);
    private:
        int m;
        char a[3];
        char *asd;
    };
    
    void Test::print(int a)
    {
        cout << "print" <<endl;
    }
    
    Test::~Test()
    {
        cout << "~Test" <<endl;
    }
    
    void Test::output(int a)
    {
        cout << "Virtual output" <<endl;
    }
    
    int main()
    {
        Test asd(1);
        asd.output(1);
        return 0;
    }

    结论:可以顺利运行,但此时它能算是静态成员函数的多态吗?

     

    情况三,静态成员函数重载:

    #include <iostream>
    #include<cstdio>
    using namespace std;
    
    class Test
    {
    public:
        Test(int a){ m = a;}
        ~Test();
        virtual void print(int a);
        static void output(){ cout << "Static output" <<endl;}
        static void output(int a){ cout << "Static output with int" <<endl;}
    private:
        int m;
        char a[3];
        char *asd;
    };
    
    void Test::print(int a)
    {
        cout << "print" <<endl;
    }
    
    Test::~Test()
    {
        cout << "~Test" <<endl;
    }
    
    
    
    int main()
    {
        Test asd(1);
        asd.output();
        asd.output(1);
        return 0;
    }

    结论:静态成员函数的多态可以通过重载来实现。

     

    D选项情况如下:显示函数不能同时声明为静态和虚函数。

    #include <iostream>
    #include<cstdio>
    using namespace std;
    
    class Test
    {
    public:
        Test(int a){ m = a;}
        ~Test();
        virtual void print();
        template <class T>
        void print(T a)
        {
            cout << "Template print" <<endl;
        }
    private:
        int m;
        char a[3];
        char *asd;
    };
    
    void Test::print()
    {
        cout << "Print" <<endl;
    }
    
    Test::~Test()
    {
        cout << "~Test" <<endl;
    }
    
    
    
    int main()
    {
        Test asd(1);
        asd.print();
        asd.print(1);
        return 0;
    }

    结论:一个类成员函数可以同时声明为模板函数和虚函数。

     

     

    静态成员函数的使用规则

    1.通过类名调用静态成员函数和非静态成员函数

    //例子一:通过类名调用静态成员函数和非静态成员函数
    class Point{
    public:
        void init(){}
        static void output(){}
    };
    
    void main()
    {
        Point::init();
        Point::output();
    }

    编译出错:错误 1 error C2352: “Point::init”: 非静态成员函数的非法调用。结论一:不能通过类名来调用类的非静态成员函数

     

    2.通过类的对象调用静态成员函数和非静态成员函数

    //例子二:通过类的对象调用静态成员函数和非静态成员函数
    class Point{
    public:
        void init(){}
        static void output(){}
    };
    
    void main()
    {
        Point pt;
        pt.init();
        pt.output();
    }

    编译通过。结论二:类的对象可以使用静态成员函数和非静态成员函数。

     

    3.在类的静态成员函数中使用类的非静态成员

    //例子三:在类的静态成员函数中使用类的非静态成员
    #include <iostream>
    using namespace std;
    
    class Point{
    public:
        void init(){}
        static void output(){
            cout << "m_x=" << m_x << endl;
        }
    private:
        int m_x;
    };
    
    void main()
    {
        Point pt;
        pt.output();
    }

    编译出错:IntelliSense: 非静态成员引用必须与特定对象相对因为静态成员函数属于整个类,在类实例化对象之前就已经分配空间了,而类的非静态成员必须在类实例化对象后才有内存空间,所以这个调用就会出错,就好比没有声明一个变量却提前使用它一样。结论三:静态成员函数中不能引用非静态成员

     

    4.在类的非静态成员函数中使用类的静态成员

    //例子四:在类的非静态成员函数中使用类的静态成员
    #include <iostream>
    using namespace std;
    
    class Point{
    public:
        void init()
        {
            output();
        }
        static void output(){}
    private:
        int m_x;
    };
    
    void main()
    {
        Point pt;
        pt.init();
    }

    编译通过。结论四:类的非静态成员可以调用静态成员函数,反之不能。

     

    5.使用类的静态成员变量

    //例子五:使用类的静态成员变量
    #include <iostream>
    using namespace std;
    
    class Point{
    public:
        Point(){
            m_nPointCount++;
        }
        ~Point(){
            m_nPointCount++;
        }
        static void output(){
            cout << "m_nPointCount=" << m_nPointCount << endl;
        }
    private:
        static  int m_nPointCount;
    };
    
    //类外初始化静态成员变量时,不用带static关键字
    int Point::m_nPointCount = 0;
    void main()
    {
        Point pt;
        pt.output();
    }

    结论五:类的静态成员变量必须先初始化再使用

     

     

    虚函数的工作原理

    另一篇博文《虚函数与纯虚函数》,内容是参考网上的文章和例子。讲述了虚函数和纯虚函数的不同,但并没有涉及到原理。在解决第二个问题前,应先了解以下知识。以下内容源自《C++ Primer Plus》中的 13.4。程序调用函数时,将使用哪个可执行代码块?编译器负责回答这个问题。将源代码中的函数调用解释为执行特定的函数代码块被称为函数名联编。在 C 语言中,这非常简单,因为每个函数名都对应一个不同的函数。在 C++ 中,由于函数重载的缘故,这项任务更复杂。编译器必须查看函数参数以及函数名才能确定使用哪个函数。然而,C/C++ 编译器可以在编译过程完成这种联编。在编译过程中进行的联编被称为静态联编,又称为早期联编。然而,虚函数使这项工作变得更困难。因为编译器不知道用户将选择什么对象,也不知道使用哪一个函数进行操作。所以,编译器必须生成能够在程序运行时选择正确的虚方法的代码,即在运行过程中进行的联编被称为动态联编,又称为晚期联编。

    C++ 规定了虚函数的行为,但将现实方法留给了编译器。不需要知道实现方法就可以使用虚函数,但了解虚函数的工作原理有助于更好地理解概念。通常,编译器处理虚函数的方法是:给每个对象添加一个隐藏成员。隐藏成员中保存了一个指向函数地址数组的指针。这种数组称为虚函数表(virtual function table,vtbl)。虚函数表中存储了为类对象进行声明的虚函数的地址。例如,基类对象包含一个指针,该指针指向基类中所有虚函数的地址表。派生类对象将包含一个指向独立地址表的指针。如果派生类提供了虚函数的新定义,该虚函数表将保存新函数的地址;如果派生类没有重新定义虚函数,该 vtbl 将保存函数原始版本的地址。如果派生类定义了新的虚函数,则该函数的地址也将被添加到 vtbl 中。注意,无论类中包含的虚函数是 1 个还是 10 个,都只需要在对象中添加 1 个地址成员,只是表的大小不同而已。

    使用虚函数时,程序将查看存储在对象中的 vtbl 地址,然后转向相应的函数地址表。如果使用类声明中定义的第一个虚函数,则程序将使用数组中的第一个函数地址,并执行具有该地址的函数。如果使用类声明中的第三个虚函数,程序将使用地址为数组中第三个元素的函数。总之,使用虚函数时,在内存和执行速度方面有一定的成本,包括:

    每个对象都将增大,增大量为存储地址的空间;

    对于每个类,编译器都将创建一个虚函数地址表(数组);

    对于每个函数调用,都需要执行一项额外的操作,即到表中查找地址。

     

    代码:

    #include <iostream>
    #include <cstdio>
    using namespace std;
    
    typedef void(*Fun)(void);
    
    class Base {
    public:
        virtual void f() { cout << "Base::f" << endl; }
        virtual void g() { cout << "Base::g" << endl; }
    };
    
    class Derive : public Base{
    public:
        virtual void f() { cout << "Deriver::f" << endl; }
    };
    
    int main()
    {
        Base a;
        Derive b;
    
        cout << "Base虚函数表地址:" << (int*)(&a) << endl;
        cout << "Base虚函数表的第一个函数地址:" << (int*)*(int*)(&a) << endl;
        cout << "Base虚函数表的第二个函数地址:" << (int*)*(int*)(&(a)+1) << endl;
    
        cout << "Derive虚函数表地址:" << (int*)(&(b)) << endl;
        cout << "Derive虚函数表的第一个函数地址:" << (int*)*(int*)(&(b)) << endl;
        cout << "Derive虚函数表的第二个函数地址:" << (int*)*(int*)(&(b)+1) << endl;
    
        return 0;
    }

    通过这个示例,我们可以看到,我们可以通过强行把 &b 转成 int,取得虚函数表的地址,然后,再次取址就可以得到第一个虚函数的地址了,也就是 Base::f(),这在上面的程序中得到了验证( 把 int 强制转成了函数指针)。我在不同的平台下运行了上述代码,得到了不太一样的结果,原因是编译器不一样。左边是 CodeBlocks 所运行的结果,右边是 Linux 运行的结果。Linux 平台下运行的结果和《C++ Primer Plus》中的表述的虚函数机制相同。

    以下结论基于 Linux 平台:

    一般继承(无虚函数覆盖)

    (1) 虚函数按照其声明顺序放于表中。 
    (2) 父类的虚函数在子类的虚函数前面。

    一般继承(有虚函数覆盖)

    (1) 覆盖的 f() 函数被放到了虚表中原来父类虚函数的位置。 
    (2) 没有被覆盖的函数依旧。

     

     

    有关虚函数注意事项

    1. 构造函数

    构造函数不能是虚函数。创建派生类对象时,将调用派生类的构造函数,而不是基类的构造函数,然后,派生类的构造函数将使用基类的一个构造函数,这种顺序不同于继承机制。因此,派生类不继承基类的构造函数,所以将类构造函数声明为虚的没有意义。

     

    2. 析构函数

    析构函数应当是虚函数,除非类不用做基类。试想这么一种情况:类对象中有 new 分配的内存,如何根据不同的情况释放相应的内存以防止内存泄露?此时,将析构函数声明为虚函数能很好解决这一问题(将相应的 delete 语句放在析构函数中)。

     

    3. 友元

    友元不能是虚函数,因为友元不是类成员,而只有成员才能是虚函数。如果由于这个原因引起了设计问题,可以通过让友元函数使用虚成员函数来解决。

     

    4. 没有重新定义

    如果派生类没有重新定义函数,将使用该函数的基类版本。如果派生类位于派生链中,则将使用最新的虚函数版本。

     

    5. 重新定义将隐藏方法

    假设创建了如下所示代码:

    #include <iostream>
    #include<cstdio>
    using namespace std;
    
    class A
    {
    public:
        virtual void show(int a) const{cout << "A" <<endl;}
    };
    
    class B : public A
    {
    public:
        virtual void show() const{cout << "B" <<endl;}
    };
    
    int main()
    {
        B a;
        a.show();
        a.show(1);
        return 0;
    }

    这将导致以下问题:

    新定义将 show() 定义为一个不接受任何参数的函数。重新定义不会生成函数的两个重载版本,而是隐藏了接受一个 int 参数的基类版本。总之,重新定义继承的方法并不是重载。如果重新定义派生类中的函数,将不只是使用相同的函数参数列表覆盖基类声明,无论参数列表是否相同,该操作将隐藏所有的同名基类方法。

    这引出了两条经验规则:第一,如果重新定义继承的方法,应确保与原来的原型完全相同,但如果返回类型是基类引用或指针,则可以修改为指向派生类的引用或指针。这种特性被称为返回类型协变(只适用于返回值,而不适用于参数),因为允许返回类型随类类型的变化而变化:

    class A
    {
    public:
        virtual A show(int a) const{cout << "A" <<endl;}
    };
    
    class B : public A
    {
    public:
        virtual B show(int a) const{cout << "B" <<endl;}
    };

    第二,如果基类声明被重载了,则应在派生类中重新定义所有的基类版本。

    class A
    {
    public:
        virtual void show(int a) const;
        virtual void show(double b) const;
        virtual void show() const;
    };
    
    class B : public A
    {
    public:
        virtual void show(int a) const;
        virtual void show(double b) const;
        virtual void show() const;
    };

    如果只重新定义一个版本,则另外几个版本将被隐藏,派生类对象将无法使用它们。注意,如果不需要修改,则新定义可只调用基类版本:void B::show() const { A::show(); }

     

    第二题运行结果如下:

    其实这道题很简单,经常在牛客网上看到类似的计算类占多少个字节的题。但这种考到虚函数的就我而言还是第一次遇到。要是不知道虚函数指针内存对齐,可能就没有办法选出正确的答案。作为复习,其他类型的大小如下:

    #include <iostream>
    #include <string>
    using namespace std;
    int main()
    {
    	cout << "short:  " << sizeof(short) <<endl;
    	cout << "short*: " << sizeof(short*) <<endl;
        cout << "int:    " << sizeof(int) <<endl;
    	cout << "int*:   " << sizeof(int*) <<endl;
    	cout << "long:   " << sizeof(long) <<endl;
    	cout << "long*:  " << sizeof(long*) <<endl;
    	cout << "float:  " << sizeof(float) <<endl;
    	cout << "float*: " << sizeof(float*) <<endl;
    	cout << "double: " << sizeof(double) <<endl;
    	cout << "double*:" << sizeof(double*) <<endl;
    	cout << "char:   " << sizeof(char) <<endl;
    	cout << "char*:  " << sizeof(char*) <<endl;
    	cout << "String: " << sizeof(string) <<endl;
        return 0;
    }

    注意:指针只与系统有关,与类型无关

     

    参考:https://www.cnblogs.com/codingmengmeng/p/5906282.html

    展开全文
  • Python:普通成员函数、类成员函数、静态函数 Python 中的类也是一个普通对象,如果需要直接使用这个类,例如将类作为参数传递到其他函数中,又希望在实例化这个类之前就能提供某些功能,那么最简单的办法就是使用 ...

    Python:普通成员函数、类成员函数、静态函数

    Python 中的类也是一个普通对象,如果需要直接使用这个类,例如将类作为参数传递到其他函数中,又希望在实例化这个类之前就能提供某些功能,那么最简单的办法就是使用 classmethod 和 staticmethod。这两者的区别在于在存在类的继承的情况下对多态的支持不同。所以本质上来说,面向对象中实例方法有哪些作用,classmethod 也就有哪些作用,只是这个面向的“对象”是类本身而已。C++ 中的 static method 只有命名空间的作用,而 Python 中不管是 classmethod 还是 staticmethod 都有 OOP(面向对象的程序设计,Object Oriented Programming)、多态上的意义。

    class Foo:
        def plain_func(self): # 普通方法
            print("plain_func")
    
        @classmethod
        def class_func(cls):  # 类方法
            print("class_func")
    
        @staticmethod
        def static_func():    # 静态方法
            print("static_func")
    
    # 必须实例化进行调用
    foo = Foo()
    foo.plain_func()
    # 可通过类名进行调用
    Foo.class_func()
    Foo.static_func()
    

    1、普通成员函数

    普通成员函数是最一般的方法,从调用方式来看,普通函数只能在类的实例中被调用,而后两者可以通过类名进行调用。

    class Kls(object):
        def __init__(self, data):
            self.data = data
        def printd(self):
            print(self.data)
    ik1 = Kls('arun')
    ik2 = Kls('seema')
    >>> ik1.printd()
    arun
    >>> ik2.printd()
    seema

     

    2、类成员函数(classmethod)

    classmethod 增加了一个 cls 参数,它引用了一个类实例。cls 类似于类中其他函数的 self 参数,例如 __init__(self),只不过 self 代表创建的实例对象,而 cls 代表类本身。classmethod 可以用于写一个只在类中运行而不在实例中运行的方法,直接通过类进行调用不管这个方法是从实例调用还是从类调用,它都用第一个参数把类传递过来对类的用户可见的功能可使用 classmethod。

    好处:

    • 方法可以判断出自己是通过基类被调用,还是通过某个子类被调用;
    • 通过子类调用时,方法可以返回子类的实例而非基类的实例;
    • 通过子类调用时,方法可以调用子类的其他 classmethod。
    # 扩散了类代码的关系到类定义的外面,以后难以维护代码
    def get_no_of_instances(cls_obj):
        return cls_obj.no_inst
    class Kls(object):
        no_inst = 0
        def __init__(self):
            Kls.no_inst = Kls.no_inst + 1
    ik1 = Kls()
    ik2 = Kls()
    >>> print(get_no_of_instances(Kls)) # 从类调用
    2
    
    
    def iget_no_of_instance(ins_obj):
        return ins_obj.__class__.no_inst
    class Kls(object):
        no_inst = 0
        def __init__(self):
            Kls.no_inst = Kls.no_inst + 1
    ik1 = Kls()
    ik2 = Kls()
    >>> print iget_no_of_instance(ik1) # 从实例调用
    2
    
    
    # 使用 classmethod
    class Kls(object):
        no_inst = 0
        def __init__(self):
            Kls.no_inst = Kls.no_inst + 1
        @classmethod
        def get_no_of_instance(cls_obj):
            return cls_obj.no_inst
    ik1 = Kls()
    ik2 = Kls()
    >>> print ik1.get_no_of_instance() # 从实例调用
    2
    >>> print Kls.get_no_of_instance() # 从类调用
    2

     

    3、静态函数(staticmethod)

    staticmethod 用于跟类有关系的功能但在运行时又不需要实例和类参与的情况,比如更改环境变量或者修改其他类的属性等。在通过类调用时,staticmethod 与 classmethod 对于调用者来说是不可区分的。

    好处:

    • 调用时返回的是一个真正的函数,而且每次调用时返回同一个实例(classmethod 则会对基类和子类返回不同的 bound method 实例)
    # 同样会扩散类内部的代码,造成维护困难
    IND = 'ON'
    def checkind():
        return (IND == 'ON')
    class Kls(object):
        def __init__(self,data):
            self.data = data
        def do_reset(self):
            if checkind():
                print('Reset done for:', self.data)
        def set_db(self):
            if checkind():
                self.db = 'new db connection'
                print('DB connection made for:',self.data)
    ik1 = Kls(12)
    >>> ik1.do_reset()
    Reset done for: 12
    >>> ik1.set_db()
    DB connection made for: 12
    
    
    # 使用 staticmethod
    IND = 'ON'
    class Kls(object):
        def __init__(self, data):
            self.data = data
        @staticmethod
        def checkind():
            return (IND == 'ON')
        def do_reset(self):
            if self.checkind():
                print('Reset done for:', self.data)
        def set_db(self):
            if self.checkind():
                self.db = 'New db connection'
            print('DB connection made for: ', self.data)
    ik1 = Kls(12)
    >>> ik1.do_reset()
    Reset done for: 12
    >>> ik1.set_db()
    DB connection made for: 12

     

    classmethod 与 staticmethod

    这两个方法的用法是类似的,在大多数情况下,classmethod 也可以通过 staticmethod 代替,staticmethod 也可以在子类上被重写为 classmethod,反之亦然。

    class Kls(object):
        def __init__(self, data):
            self.data = data
        def printd(self):
            print(self.data)
        @staticmethod
        def smethod(*arg):
            print('Static:', arg)
        @classmethod
        def cmethod(*arg):
            print('Class:', arg)
     
    >>> ik = Kls(23)
    >>> ik.printd()
    23
    >>> ik.smethod()
    Static: ()
    >>> ik.cmethod()
    Class: (<class '__main__.Kls'>,)
    
    >>> Kls.printd()
    TypeError: unbound method printd() must be called with Kls instance as first argument (got nothing instead)
    >>> Kls.smethod()
    Static: ()
    >>> Kls.cmethod()
    Class: (<class '__main__.Kls'>,)

    Python 中的 classmethod 和 staticmethod 有什么具体用途?

    展开全文
  • C++ 运算符重载:成员、非成员函数重载

    千次阅读 多人点赞 2020-06-19 19:41:34
    C++ 运算符重载运算符重载1、背景2、运算符函数重载的两种形式1、成员函数重载1、定义格式2、非成员函数重载(友元)1、定义格式3、重载原则4、参数和返回值5、成员函数重载1、双目运算符重载1、定义2、调用格式2、...
  • 类的成员函数(简称类函数)是函数的一种,它的用法和作用和前面介绍过的函数基本上是一样的,它也有返回值和函数类型,它与一般函数的区别只是:它是属于一个类的成员,出现在类体中。它可以被指定为private(私有的...
  • (3) 重载运算符: 作为成员函数还是非成员函数 对于很多运算符来说,可以选择使用成员函数或非成员函数来实现运算符重载。 一般来说,非成员函数应是友元函数,这样它才能直接访问类的私有数据。 例如,Time类的...
  • 【C++】成员函数

    千次阅读 多人点赞 2019-06-02 19:48:42
    C++成员函数(Member Functions) 目录 C++成员函数(Member Functions) 1、成员函数定义(Member Function Definition) 2、内联(Inline) 3、常成员函数(Const Member Functions) 4、使用对象指针...
  • 运算符重载 对于面向对象的程序设计来说,运算符重载可以完成两个对象之间的复杂操作...为了重载运算符,首先要定义运算符重载函数,它通常是类的非静态成员函数或者友元函数,运算符的操作数通常也应为对象。 定...
  • 首先我们定义一个类Ctest,类里面包含三个不同形式的成员函数,静态成员函数statFunc()、动态成员函数dynFunc()和虚拟函数virtFunc()。在main函数中我们利用cout标准输出流分别输出这三个函数的地址,程序如下所示:...
  • 类的成员函数(简称类函数)是函数的一种,它的用法和作用和前面介绍过的函数基本上是一样的,它也有返回值和函数类型,它与一般函数的区别只是:它是属于一个类的成员,出现在类体中。它可以被指定为private(私有的...
  • 关于静态成员函数

    千次阅读 多人点赞 2017-09-19 14:02:55
    关于静态成员函数 静态成员是受到private限定符的作用的,若用private修改,不可外部访问的  static对象如果出现在类中,那么该对象即使从未被使用到,它也会被构造以及析构。而函数中的static对象,如果该函数...
  • C++学习之全局函数和成员函数

    千次阅读 2019-09-16 15:46:48
    1.把全局函数转化成成员函数 通过this指针隐藏左操作数 Test add(Test &t1, Test &t2)===》Test add(Test &t2) 案例:实现 t1 = t1 + t2 class Test { public: Test(int a = 0,...
  • 转自:http://blog.chinaunix.net/uid-10673338-id-2936852.html 转自:...   对以上两篇文章,我添加了自己已有的部分知识,并重新地汇总整理 ... ...从函数定义的位置来粗略理解
  • C++中,静态成员函数不能被声明为virtual函数。  例如,下面的程序会编译失败。 #include&lt;iostream&gt; class Test { public: // 编译错误:static成员函数不能声明为virtual virtual static ...
  • C++ 类静态成员函数

    万次阅读 2020-12-06 18:25:14
    。。。
  • 即因为一个类的静态成员函数调用了类的非静态成员变量,而报错。 下面具体介绍一些相关知识点,以防下次再出错。 类成员函数当回调函数的方法 参考自:...
  • 数据成员: 静态数据成员是类的一...静态函数成员与非静态函数成员都为类所有,对象中并不存在函数的拷贝(每个对象所占用的存储空间只是该对象的数据成员所占用的存储空间,但是在逻辑上函数和数据是一起被封装进...
  • c++成员函数指针的本质

    千次阅读 2018-06-16 11:19:57
    关于c++成员函数指针的声明,类型定义,赋值和调用就不再赘述了,需要了解的朋友可以看这篇文章: ...现在我要证明,在我的编译环境中,所看到的c++的成员函数指针...
  • 成员函数:无论静态函数还是非静态函数,都是属于类的(这一点与数据成员的静态非静态不同),对象并不拥有函数的拷贝.两者的区别在于:非静态的函数由类对象(加.或指针加-&gt;;)调用,这时将向函数传递this指针.而静态...
  • c++类的默认成员函数

    千次阅读 2019-05-14 21:23:08
    6个默认成员函数 这些默认的类成员函数, 在类创建的时候, 编译器会自动调用, 但是自动调用并不意味着他们能完成用户所期待的所有任务, 像构造函数, 析构函数, 拷贝构造函数, 赋值重载会在某些情况下需要用户根据...
  • C++函数编译原理和成员函数的实现

    千次阅读 2017-07-25 15:34:54
    对象的内存中只保留了成员变量,除此之外没有任何其他信息,程序运行时不知道 stu 的类型为 Student,也不知道它还有四个成员函数 setname()、setage()、setscore()、show(),C++ 究竟是如何通过对象调用成员函数...
  • 在类外定义成员函数 成员函数可以在类体中直接定义。也可以在类体中只写成员函数的声明,而在类的外面进行函数定义。如: class Student { public : void display( ); //公用成员函数原型声明 private : int ...
  • 静态成员函数调用

    千次阅读 2019-03-19 12:13:10
    static成员函数属于类,独立于对象存在,不依赖于对象。随着类的加载而加载,优先于对象存在,被所有对象共享,可以直接被类名调用。 调用方式: 类名::成员名 对象名.成员名 static成员函数中没有this指针,...
  • **************************数据成员的初始化 *******************************************************************************************************************1、数据成员是否能在定义类的时候就初始化?...
  • C++ 类成员函数的函数指针

    万次阅读 多人点赞 2018-08-23 18:40:17
    当我们在 C++ 中直接像 C 那样使用类的成员函数指针时,通常会报错,提示你不能使用非静态的函数指针: reference to non-static member function must be called 两个解决方法: 把非静态的成员方法改成静态的...
  • c++类成员函数作为回调函数

    万次阅读 2018-06-09 19:27:51
    最近又仔细学了会,感觉回调函数,我认为就是将一个函数指针A作为参数传入另外一个函数B,然后在函数B中调用函数A。 普通回调 具体先看一个简单的例子: [cpp] view plain copy#include&lt;stdio.h&gt;...
  • 静态成员函数调用方式--收藏帖子

    千次阅读 2018-12-13 17:14:56
    静态成员函数调用方式 A:: s_fun();//不需要额外传递一个参数,作为this 指针;因为静态函数,属于类,不属于具体对象。非静态成员函数调用方式 。非静态成员函数,属于对象,需要隐式传递 this 指针作为参数...
  • 浅谈C++类中6个成员函数

    千次阅读 多人点赞 2021-03-18 15:45:13
    六个默认的成员函数构造函数浅谈深挖析构函数浅谈深挖拷贝构造函数浅谈深挖赋值重载函数浅谈深挖取地址重载函数const修饰的取地址重载函数二级目录 构造函数 浅谈 构造函数是一个特殊的成员函数,名字与类名相同且不...
  • 成员函数:无论静态函数还是非静态函数,都是属于类的(这一点与数据成员的静态非静态不同),对象并不拥有函数的拷贝.两者的区别在于:非静态的函数由类对象(加.或指针加->;)调用,这时将向函数传递this指针.而静态函数由...

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 1,114,578
精华内容 445,831
关键字:

成员函数