面试(二)

面试内容:

1、链表定义,反转链表

/**
 * Definition for singly-linked list.
 * struct ListNode {
 *     int val;
 *     ListNode *next;
 *     ListNode(int x) : val(x), next(NULL) {}
 * };
 */
class Solution {
public:
    ListNode* reverseList(ListNode* head) {
 
    }
};

反转链表

#include <iostream>
 
using namespace std;
 
//Definition for singly - linked list.
struct ListNode {
	int val;
	ListNode *next;
	ListNode(int x) : val(x), next(NULL) {}
};
 
class Solution {
public:
	ListNode* reverseList(ListNode* head) {
		ListNode* prev = nullptr;
		ListNode* curr = head;
		while (curr) {
			ListNode* next = curr->next;
			curr->next = prev;
			prev = curr;
			curr = next;
		}
		return prev;
	}
};
 
/*打印链表*/
void printList(ListNode* head)
{
	ListNode* phead = head;
	while (phead != NULL)
	{
		cout << phead->val << " ";
		phead = phead->next;
	}
	cout << "\n";
}
 
 
int main()
{
	//链表赋值
	int a[5] = {1,2,3,4,5};
	ListNode *La = new ListNode(a[0]);
	ListNode* phead = La;
 
	for (int i = 1; i < 5; i++)
	{
		ListNode* newnode = new ListNode(a[i]);
		phead->next = newnode;//并将其赋值给La
		phead = newnode;
	} //链表赋值
 
	Solution sln;
	printList(La);
	ListNode* Lc = sln.reverseList(La);
	printList(Lc);
	return 0;
}

2、删除链表倒数第k个节点

# include <bits/stdc++.h>
using namespace std;

struct list_node{
    int val;
    struct list_node * next;
}; //链表的节点

int K;

list_node * input_list(void) //读入链表
{
    int n, val;
    list_node * phead = new list_node();
    list_node * cur_pnode = phead;
    scanf("%d %d", &n, &K);
    for (int i = 1; i <= n; ++i) {
        scanf("%d", &val);
        if (i == 1) {
            cur_pnode->val = val;
            cur_pnode->next = NULL;
        }
        else {
            list_node * new_pnode = new list_node();
            new_pnode->val = val;
            new_pnode->next = NULL;
            cur_pnode->next = new_pnode;
            cur_pnode = new_pnode;
        }
    }
    return phead;
}

list_node * remove_last_kth_node(list_node * head, int K)
{
    //在下面完成代码
    int n = 1;
    list_node * p = head;
    while (p && n < K) {
        n += 1;
        p = p->next;
    }
    if (!p) return head;
    list_node * q = head;
    list_node * q_pre = head;
    while (p->next) {
        p = p->next;
        q_pre = q;
        q = q->next;
    }
    q_pre->next = q->next;
    delete q;
    return head;
}

void print_list(list_node * head)
{
    while (head != NULL) {
        printf("%d ", head->val);
        head = head->next;
    }
}

int main ()
{
    list_node * head = input_list(); // 链表的头节点
    list_node * rhead = remove_last_kth_node(head, K);
    print_list(rhead);
    return 0;
}

参考:在链表中删除倒数第K个节点

3、二叉搜索树排列n个元素组合数

方法一:动态规划

class Solution {
public:
    int numTrees(int n) {
        vector<int> G(n + 1, 0);
        G[0] = 1;
        G[1] = 1;

        for (int i = 2; i <= n; ++i) {
            for (int j = 1; j <= i; ++j) {
                G[i] += G[j - 1] * G[i - j];
            }
        }
        return G[n];
    }
};

方法二、数学

class Solution {
public:
    int numTrees(int n) {
        long long C = 1;
        for (int i = 0; i < n; ++i) {
            C = C * 2 * (2 * i + 1) / (i + 2);
        }
        return (int)C;
    }
};

参考:不同的二叉搜索树
参考:96. 不同的二叉搜索树

4、虚函数

  1. 什么是虚函数(virtual)?
  • 在C++语言中,基类将类型相关的函数与派生类不做改变直接继承的函数区分对待,对于某些函数,基类希望它的派生类各自定义适合自身的版本,此时基类就将这些函数声明成虚函数。

  • 简单的说,virtual是用来解决基类与派生类函数重名的问题, 当不使用虚函数时,调用的是基类定义的函数,使用虚函数时,则调用的是派生类定义的函数。

参考:virtual 虚函数用法详解

5、堆栈的使用

a.内存管理方式不同
对于栈来讲,是由编译器自动管理,无需我们手动控制;对于堆来说,释放工作由程序员控制,容易产生内存泄漏。
b.空间大小不同
一般来讲在32位系统下,堆内存可以达到4G的空间,从这个角度来看堆内存几乎是没有什么限制的。但是对于栈来讲,一般都是有一定的空间大小的,空间大小远小于堆内存
堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。
c. 产生碎片不同
对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低。对于栈来讲,则不会存在这个问题,因为栈是先进后出的队列,他们是如此的一一对应,以至于永远都不可能有一个内存块从栈中间弹出,在它弹出之前,在他上面的后进的栈内容已经被弹出.
d.分配方式不同
堆都是动态分配的,没有静态分配的堆。栈有静态分配和动态分配, 静态分配是编译器完成的,比如局部变量的分配。动态分配由 malloc 函数进行分配,但是栈的动态分配和堆是不同的,它的动态分配是由编译器进行释放,无需我们手工实现。
e. 分配效率不同
栈的效率比堆高很多。栈是机器系统提供的数据结构,计算机在底层提供栈的支持,分配专门的寄存器来存放栈的地址,压栈出栈都有相应的指令,因此比较快。堆是由库函数提供的,机制很复杂,库函数会按照一定的算法进行搜索内存,因此比较慢。
参考:C/C++ 程序内存分配,内存中堆和栈使用情况以及函数压栈退栈过程

6、c++与c的异同

c面向过程,c++面型对象。c中可以通过结构体实现对象功能。

7、c++与python、java异同

c++是编译型语言,python和java是解释型语言。都支持面型对象编程。

8、c++ 的封装、继承和多态

参考:对封装、继承、多态的理解

9、yolo的参数量

10、caffe、tensorflow、pytorch等框架的优缺点。

11、多线程通信

1.互斥锁
  mutex;
  lock_guard (在构造函数里加锁,在析构函数里解锁)
  unique_lock 自动加锁、解锁

2.读写锁
  shared_lock

3.信号量
  c++11中未实现,可以自己使用mutex和conditon_variable 实现

参考:c++ 线程间通信方式

12、多进程通信。

参考:多进程编程之进程间通信

13、算法:逻辑斯蒂回归,朴素贝叶斯,svm的原理

14、c++: 继承和接口 虚函数和纯虚函数

15、数据结构: 链表

python3版链表定义以及链表反转

# -*- coding : UTF-8 -*-
# @file   : rever_linklist.py
# @Time   : 2021/6/18 21:02
# @Author : wmz


class Node(object):
    def __init__(self, elem, next_=None):
        self.elem = elem
        self.next = next_


def reverseList(head):
    if head is None or head.next is None:  # 若链表为空或者仅为一个数就直接返回
        return head
    pre = None
    next = None
    while(head != None):
        next = head.next
        head.next = pre
        pre = head
        head = next
    return pre


if __name__ == "__main__":
    l1 = Node(3)  # 建立链表 3->2->1->9->None
    l1.next=Node(2)
    l1.next.next = Node(1)
    l1.next.next.next = Node(9)
    l = reverseList(l1)
    print(l.elem, l.next.elem, l.next.next.elem, l.next.next.next.elem)

参考:单链表反转_python版

python3删除链表倒数第k个元素

# -*- coding : UTF-8 -*-
# @file   : removeKthfromEnd.py
# @Time   : 2021/6/19 9:16
# @Author : wmz


class ListNode:
    def __init__(self, val=0, next=None):
        self.val = val
        self.next = next


class Solution:
    def removeNthFromEnd(head: ListNode, n: int) -> ListNode:
        dummy = ListNode(0, head)
        first = head
        second = dummy
        for i in range(n):
            first = first.next

        while first:
            first = first.next
            second = second.next

        second.next = second.next.next
        return dummy.next


def cvt_linklist2list(head):
    if not head or not head.next:
        return []
    result = []
    while head:
        result.append(head.val)
        head = head.next
    return result


if __name__ == "__main__":
    a = [1, 2, 3, 4, 5]
    n = len(a)
    l = ListNode(a[0])
    head = l
    for m in a[1:n]:
        head.next = ListNode(m)
        head = head.next
    sln = Solution
    l2 = sln.removeNthFromEnd(l, 2)
    list1 = cvt_linklist2list(l2)
    print(list1)
    print("process over!")

16、硬件:cpu构成 ram和rom

系统程序工作区和用户程序区都是RAM,系统程序区是ROM
在这里插入图片描述

ROM、RAM的区别:
ROM(只读存储器或者固化存储器)

RAM(随机存取存储器)

ROM和RAM指的都是半导体存储器,ROM是Read Only Memory的缩写,RAM是Random Access Memory的缩写。ROM在系统停止供电的时候仍然可以保持数据,而RAM通常都是在掉电之后就丢失数据,典型的RAM就是计算机的内存。

RAM有两大类,一种称为静态RAM(Static RAM/SRAM),当数据被存入其中后不会消失。SRAM速度非常快,是目前读写最快的存储设备了。当这个SRAM 单元被赋予0 或者1 的状态之后,它会保持这个状态直到下次被赋予新的状态或者断电之后才会更改或者消失。但是存储1bit 的信息需要4-6 只晶体管。因此它也非常昂贵,所以只在要求很苛刻的地方使用,譬如CPU的一级缓冲,二级缓冲。另一种称为动态RAM(Dynamic RAM/DRAM),DRAM 必须在一定的时间内不停的刷新才能保持其中存储的数据。DRAM 只要1 只晶体管就可以实现。DRAM保留数据的时间很短,速度也比SRAM慢,不过它还是比任何的ROM都要快,但从价格上来说DRAM相比SRAM要便宜很 多,计算机内存就是DRAM的。

DRAM分为很多种,常见的主要有FPRAM/FastPage、EDORAM、SDRAM、DDR RAM、RDRAM、SGRAM以及WRAM等,这里介绍其中的一种DDR RAM。DDR RAM(Date-Rate RAM)也称作DDR SDRAM,这种改进型的RAM和SDRAM是基本一样的,不同之处在于它可以在一个时钟读写两次数据,这样就使得数据传输速度加倍了。这是目前电脑中用 得最多的内存,而且它有着成本优势,事实上击败了Intel的另外一种内存标准-Rambus DRAM。在很多高端的显卡上,也配备了高速DDR RAM来提高带宽,这可以大幅度提高3D加速卡的像素渲染能力。

ROM也有很多种,PROM是可编程的ROM,PROM和EPROM(可擦除可编程ROM)两者区别是,PROM是一次性的,也就是软件灌入后,就无法修 改了,这种是早期的产品,现在已经不可能使用了,而EPROM是通过紫外光的照射擦出原先的程序,是一种通用的存储器。另外一种EEPROM是通过电子擦出,价格很高,写入时间很长,写入很慢。

最初,把只能读的存储器叫做ROM(Read Only Memory),并且掉电后数据不会丢失。由于不能改写,因而使用起来很不方便。随着技术的进步,在ROM中使用一些新技术,就可以使它具有可以编程的功能。比较早的是熔丝型的可编程ROM,由于是通过熔断熔丝来编程的,所以这类ROM编程后,就不能再写了,是一次性的(OTP)。后来又出现了EPROM,是通过紫外线来擦除的,并且通过高压来编程,这类ROM上面一般有一个透明的石英玻璃窗,看上去挺漂亮的,它就是用来给紫外线照射的。后来又出现了EEPROM,不用紫外线照射就可以擦除,因而可以直接在电路中编程。另外还有FLASH ROM,又可分为NOR FLASH和NAND FLASH。FLASH ROM一般有一个特点,就是写数据时,可以将1改为0,而不能将0改为1,因而写数据前需要擦除,擦除时将所有数据置1。

参考:浅谈CPU,内核,寄存器,缓存,RAM,ROM的作用和他们之间的联系

17、网络通信层 深度学习网络结构

18、机器学习和深度学习(一般根据简历问)

19、coding:python手撸链表

问题同1~3和15

20、长度为N int 数组, 找出其中第k大的数

//长度为N int 数组, 找出其中第k大的数

int findKthlarge(int num[], int, N, int k)
{

	for(int i = 0; i < k; i++)
  {
    for(int j = i; j < N;j++)
    {
    	if(num[j] > num[i])
      {
      	int tmp = num[i];
        num[i] = num[j];
        num[j] = tmp;
      }   
    
    }
  
  }
	return num[k-1];
}

21、已知n,求 n \sqrt{n} n

迭代公式,二分法、牛顿法
牛顿法

class Solution {  
public:  
    int mySqrt(int x) {  
        if (x == 0) return 0;  
        double last=0;  
        double res=1;  
        while(res!=last)  
        {  
            last=res;  
            res=(res+x/res)/2;  
        }  
        return int(res);  
    }  
};  

二分法

class Solution {  
public:  
    int mySqrt(int x) {  
        //注:在中间过程计算平方的时候可能出现溢出,所以用long long。  
        long long i=0;  
        long long j=x/2+1;//对于一个非负数n,它的平方根不会大于(n/2+1)  
        while(i<=j)  
        {  
            long long mid=(i+j)/2;  
            long long res=mid*mid;  
            if(res==x) return mid;  
            else if(res<x) i=mid+1;  
            else j=mid-1;  
        }  
        return j;  
    }  
};  

参考:【LeetCode刷题】求平方根

22、ctc原理

参考: CTC 的工作原理
ctc要解决的是这个方法主要是解决神经网络label 和output 不对齐的问题。

  • 这种问题经常出现在scene text recognition, speech recognition, handwriting recognition 这样的应用里。 比如 Fig. 1 中的语音识别, 就会识别出很多个ww, 很多个r, 如果不做处理, 就会变成wworrrlld 我忽略了空白(blank). 有一种简单粗暴的方法就是把重复的都当做一个字母。 但是这样 就会出现不识别单词 happy 这样的单词。 这个时候, 空白的作用就非常大了, 在这里他把同一个字母隔开, 比如happy, 就会变成和hhh aaaa ppp ppp yyyy --> happy.

  • 用数学的表达式来说, 这个是一个mapping, X-> Y 而且是一个 monotonic mapping (单调映射), 同时多个X 会映射到同一个Y. 最后一个特性就比较让人头疼, 就是Y 的长度不能 长于X 的长度。 想必用过tensorflow tf.nn.ctc_loss 应该都知道这样的 error message 把

  • “No valid path found, Loss: inf ”

  • 出现这个warning/Error message 的原因就是 Y 的长度大于X 的长度, 导致CTC 无法计算。 解决方案就是检查训练集 中的label 和 实际内容是否相符。 比如, 对于 scene text recognition, 图像中是 hello, how are you doing。 而label 是 hello. 这样的训练集肯定会出问题的。
    ctc的输入是rnn的输出,长度为T宽度为label长度+1()的概率矩阵。
    在这里插入图片描述

23、yolo网络结构和原理及loss函数

这个问题比较宽泛,需要分yolov1/yolov2/yolov3/yolov4/yolov5分别说明
yolov1
yolov1 loss函数
在这里插入图片描述

yolov2
yolov2的损失函数:
只是在yolov1基础上改动了关于bbox的w和h的损失计算方式 即从:
在这里插入图片描述
去掉了w和h的二次根号,作者认为没有必要。

yolov3----darknet53网络结构

YOLOv3的损失函数:
是在yolov2基础上改动的,最大的变动是分类损失换成了二分交叉熵,这是由于yolov3中剔除了softmax改用logistic。
在这里插入图片描述

yolov4
yolov5

参考:【论文理解】yolov3损失函数

24、gdb调试 定位core dump

一,什么是coredump

    我们经常听到大家说到程序core掉了,需要定位解决,这里说的大部分是指对应程序由于各种异常或者bug导致在运行过程中异常退出或者中止,并且在满足一定条件下(这里为什么说需要满足一定的条件呢?下面会分析)会产生一个叫做core的文件。

    通常情况下,core文件会包含了程序运行时的内存,寄存器状态,堆栈指针,内存管理信息还有各种函数调用堆栈信息等,我们可以理解为是程序工作当前状态存储生成第一个文件,许多的程序出错的时候都会产生一个core文件,通过工具分析这个文件,我们可以定位到程序异常退出的时候对应的堆栈调用等信息,找出问题所在并进行及时解决。

二,coredump文件的存储位置

core文件默认的存储位置与对应的可执行程序在同一目录下,文件名是core,大家可以通过下面的命令看到core文件的存在位置: cat /proc/sys/kernel/core_pattern
注意:这里是指在进程当前工作目录的下创建。通常与程序在相同的路径下。但如果程序中调用了chdir函数,则有可能改变了当前工作目录。这时core文件创建在chdir指定的路径下。有好多程序崩溃了,我们却找不到core文件放在什么位置。和chdir函数就有关系。当然程序崩溃了不一定都产生 core文件。

如下程序代码:则会把生成的core文件存储在/data/coredump/wd,而不是大家认为的跟可执行文件在同一目录。
参考:Gdb 调试core文件详解

一、coredump:是针对程序异常而产生的core文件,包含程序运行时的内存、寄存器状态、堆栈指针、函数调用等信息,用于存储程序出错时的状态。

二、coredump的存储位置:与被执行文件在同一目录下。当然,位置可以在程序中通过 chdir 命令修改

三、如何判断是coredump文件:该文件主要的格式为 ELF 格式。可以通过

readelf -h core

coredump产生的几种情况:

  1. 内存访问越界

  2. 多线程程序使用不安全的线程函数

  3. 多线程读写的数据未加锁保护

  4. 非法指针

  5. 堆栈溢出

参考:GDB调试及coredump详解

25、gpu多卡调度,调度原理

26、c++11 特性 unique_ptr,shared_ptr使用场景

unique_ptr
如名字所示,unique_ptr是个独占指针,C++ 11之前就已经存在,unique_ptr所指的内存为自己独有,某个时刻只能有一个unique_ptr指向一个给定的对象,不支持拷贝和赋值。
shared_ptr
shared_ptr允许多个该智能指针共享“拥有”同一堆分配对象的内存,这通过引用计数(reference counting)实现,会记录有多少个shared_ptr共同指向一个对象,一旦最后一个这样的指针被销毁,也就是一旦某个对象的引用计数变为0,这个对象会被自动删除。支持复制和赋值操作。

27、static 变量作用,使用全局变量、成员变量、命名空间时的生命周期

静态变量的类型说明符是static。静态变量当然是属于静态存储方式,但是属于静态存储方式的量不一定就是静态变量,例如外部变量虽属于静态存储方式,但不一定是静态变量,必须由 static加以定义后才能成为静态外部变量,或称静态全局变量。对于自动变量,它属于动态存储方式。但是也可以用static定义它为静态自动变量,或称静态局部变量,从而成为静态存储方式。由此看来,一个变量可由static进行再说明,并改变其原有的存储方式。
1. 静态局部变量
在局部变量的说明前再加上static说明符就构成静态局部变量。例如:

static int a,b; static float array[5]={1,2,3,4,5};
静态局部变量属于静态存储方式,它具有以下特点:
(1)静态局部变量在函数内定义,但不象自动变量那样,当调用时就存在,退出函数时就消失。静态局部变量始终存在着,也就是说它的生存期为整个源程序。
(2)静态局部变量的生存期虽然为整个源程序,但是其作用域仍与自动变量相同,即只能在定义该变量的函数内使用该变量。退出该函数后,尽管该变量还继续存在,但不能使用它。
(3)允许对构造类静态局部量赋初值。若未赋以初值,则由系统自动赋以0值。
(4)对基本类型的静态局部变量若在说明时未赋以初值,则系统自动赋予0值。而对自动变量不赋初值,则其值是不定的。根据静态局部变量的特点,可以看出它是一种生存期为整个源程序的量。虽然离开定义它的函数后不能使用,但如再次调用定义它的函数时,它又可继续使用,而且保存了前次被调用后留下的值。因此,当多次调用一个函数且要求在调用之间保留某些变量的值时,可考虑采用静态局部变量。虽然用全局变量也可以达到上述目的,但全局变量有时会造成意外的副作用,因此仍以采用局部静态变量为宜。

2.静态全局变量

全局变量(外部变量)的说明之前再冠以static 就构成了静态的全局变量。全局变量本身就是静态存储方式,静态全局变量当然也是静态存储方式。这两者在存储方式上并无不同。这两者的区别虽在于非静态全局变量的作用域是整个源程序,当一个源程序由多个源文件组成时,非静态的全局变量在各个源文件中都是有效的。而静态全局变量则限制了其作用域,即只在定义该变量的源文件内有效,在同一源程序的其它源文件中不能使用它。由于静态全局变量的作用域局限于一个源文件内,只能为该源文件内的函数公用,因此可以避免在其它源文件中引起错误。从以上分析可以看出, 把局部变量改变为静态变量后是改变了它的存储方式即改变了它的生存期。把全局变量改变为静态变量后是改变了它的作用域,限制了它的使用范围。因此static 这个说明符在不同的地方所起的作用是不同的。应予以注意。

参考:static 静态变量生命周期。-=—
以上参考说的不是很清楚,下面这个参考说的比较清楚。
在C语言中,我们知道有static静态变量,生命周期与作用域都跟普通变量有所不同。而在C++的类中,也有静态成员变量同时还有静态成员函数,先来看看C++中静态成员变量与静态成员函数的语法:

#include <iostream>
#include <string>
 
using namespace std;
 
class test
{
private:
    static int m_value;		//定义类的静态成员变量
 
public:
 
    static int getValue()	//定义类的静态成员函数
    {
    	return m_value;
    }
};
 
int test::m_value = 12;		//类的静态成员变量需要在类外分配内存空间
 
int main()
{
    test t;
 
    cout << t.getValue() << endl;
    system("pause");
}

以上代码,我们在test类中分别定义了一个静态成员变量与静态成员函数,首先来看下静态成员变量

  • 静态成员变量属于整个类所有
  • 静态成员变量的生命期不依赖于任何对象,为程序的生命周期
  • 可以通过类名直接访问公有静态成员变量
  • 所有对象共享类的静态成员变量
  • 可以通过对象名访问公有静态成员变量
  • 静态成员变量需要在类外单独分配空间
  • 静态成员变量在程序内部位于全局数据区 (Type className::VarName = value)

针对静态成员变量的以上几点,我们把上边的代码修改如下,用于统计当前对象的个数:

#include <iostream>
#include <string>
 
using namespace std;
 
class test
{
private:
    static int m_value1;		//定义私有类的静态成员变量
public:
    static int m_value2;		//定义私有类的静态成员变量
 
public:
    test()
    {
    	m_value1++;
    	m_value2++;
    }
 
    int getValue()	//定义类的静态成员函数
    {
    	return m_value1;
    }
};
 
int test::m_value1 = 0;		//类的静态成员变量需要在类外分配内存空间
int test::m_value2 = 0;		//类的静态成员变量需要在类外分配内存空间
 
int main()
{
    test t1;
    test t2;
    test t3;
 
    cout << "test::m_value2 = " << test::m_value2 << endl;	//通过类名直接调用公有静态成员变量,获取对象个数
    cout << "t3.m_value2 = " << t3.m_value2 << endl;		//通过对象名名直接调用公有静态成员变量,获取对象个数
    cout << "t3.getValue() = " << t3.getValue() << endl;	//通过对象名调用普通函数获取对象个数
    system("pause");
}

在这里插入图片描述
从输出,貌似得到我们想要的效果,但是C++中讲究的是封装性,以上代码,有2个不妥之处
1、类名或对象名能直接访问成员变量,也就是说成员变量能直接被外界修改
2、我们使用了一个成员函数来获取当前的对象个数,看似没问题,但是必须要定义对象,通过对象去调用,但有时候我们不想定义对象,也能使用类中的成员函数,这就是我们要说的类的静态成员函数。

类的成员函数有如下特性:

  • 静态成员函数是类的一个特殊的成员函数
  • 静态成员函数属于整个类所有,没有this指针
  • 静态成员函数只能直接访问静态成员变量和静态成员函数
  • 可以通过类名直接访问类的公有静态成员函数
  • 可以通过对象名访问类的公有静态成员函数
  • 定义静态成员函数,直接使用static关键字修饰即可

现在我们再来修改上边的代码

#include <iostream>
#include <string>
 
using namespace std;
 
class test
{
private:
    static int m_value;		//定义私有类的静态成员变量
 
public:
    test()
    {
    	m_value++;
    }
 
    static int getValue()		//定义类的静态成员函数
    {
    	return m_value;
    }
};
 
int test::m_value = 0;		//类的静态成员变量需要在类外分配内存空间
 
int main()
{
    test t1;
    test t2;
    test t3;
 
    cout << "test::m_value2 = " << test::getValue() << endl;	//通过类名直接调用公有静态成员函数,获取对象个数
    cout << "t3.getValue() = " << t3.getValue() << endl;		//通过对象名调用静态成员函数获取对象个数
    system("pause");
}

在这里插入图片描述
这样我们就直接能通过类名去访问静态成员函数,获取对象个数,不通过任何对象。

静态成员函数在C++中的作用很强大,包括后边的介绍的单例模式、二阶构造模式,都用到静态成员函数及静态成员变量。

下边的图为静态成员函数与普通成员函数的比较
在这里插入图片描述

参考:C++中类的(static)静态成员变量与(static)静态成员函数
其他参考:

数据成员:

静态数据成员是类的一部分,为类的所有实例共享(静态区);非静态数据成员,类的每个实例都有一份拷贝(动态区)。

静态数据成员的访问:

静态数据成员是类的一部分,在产生任何实例之前已经存在。

函数成员(都在代码区):

静态函数成员与非静态函数成员都为类所有,对象中并不存在函数的拷贝(每个对象所占用的存储空间只是该对象的数据成员所占用的存储空间,但是在逻辑上函数和数据是一起被封装进对象的)。静态成员函数和非静态成员函数的根本区别在于有无this指针。非静态函数由对象名.或者对象指针->调用,调用时编译器会向函数传递this指针;静态成员函数则有类名::或者对象名.调用,没有this指针,不识别对象个体,经常用来操作类的静态数据成员。访问类的非静态成员要通过对象来实现。

内存角度分析:

类的静态成员(数据成员和函数成员)为类本身所有,在类加载的时候就会分配内存,可以通过类名直接访问;非静态成员(数据成员和函数成员)属于类的实例所有,所以只有在创建类的实例的时候才会分配内存,并通过实例去访问。

注意:类的静态数据成员是静态存储,它是静态生存周期,必须进行初始化。

注意:静态数据成员的初始化在类体外进行,前面不加static以免与一般静态变量或者对象混淆。

静态成员函数访问非静态成员报错:

类的静态成员在类加载的时候就已经分配内存,而此时类的非静态成员尚未分配内存,访问内存中不存在的东西自然会出错。

#include<iostream>
 
using namespace std;
 
class StaticTest
{
public:
	StaticTest(int a){A=a;B++;}  // B实际为类实例化的对象的个数
	static void printTest(StaticTest t);
private:
	int A;
	static int B;
};
void StaticTest::printTest(StaticTest t)
{
	cout<<"t.A: "<<t.A<<endl;
	cout<<"StaticTest::B: "<<StaticTest::B<<endl;
	cout<<"t.B: "<<t.B<<endl;
}
int StaticTest::B=0;
int main()
{
	StaticTest a1(66);
	StaticTest::printTest(a1);
	StaticTest a2(88);
	StaticTest::printTest(a2);
	return 0;
}

在这里插入图片描述

参考:C++ 静态成员函数和非静态成员函数的区别

28、识别时是否使用语言模型

29、拷贝构造和拷贝赋值区别

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

落花逐流水

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值