zoukankan      html  css  js  c++  java
  • [转载]C/C++ 面试题整理 (一)

    0. 下列两行代码有什么区别?

    char a[] = “string”;

    char *b = “string”;

    答: sizeof(a)为 7

         sizeof(b)为 4

         第一句是在栈中分配了7*sizeof(char)的空间来存放“string”a指向其首地址

         第二句是在静态存储区中生成了字符串“string”,然后在栈中生成一个指向char的指针,内存放的是“string”在静态存储区的地址。

     

    1.求下面函数的返回值(微软)

    int func(x)

    {

    int countx = 0;

    while(x)

    {

    countx ++;

    x = x&(x-1);

    }

    return countx;

    }

    假定x = 9999。 答案:8

    思路:将x转化为2进制,看含有的1的个数。

     

    2. 什么是“引用”?申明和使用“引用”要注意哪些问题?

    答:引用就是某个目标变量的“别名”(alias),对引用的操作与对变量直接操作效果完全相同。申明一个引用的时候,切记要对其进行初始化。引用声明完毕后,相当于目标变量名有两个名称,即该目标原名称和引用名,不能再把该引用名作为其他变量名的别名。声明一个引用,不是新定义了一个变量,它只表示该引用名是目标变量名的一个别名,它本身不是一种数据类型,因此引用本身不占存储单元,系统也不给引用分配存储单元。不能建立数组的引用。

     

    4. 在什么时候需要使用“常引用”? 

    如果既要利用引用提高程序的效率,又要保护传递给函数的数据不在函数中被改变,就应使用常引用。常引用声明方式:const 类型标识符 &引用名=目标变量名;

    1

    int a ;

    const int &ra=a;

    ra=1; //错误

    a=1; //正确

    2

    string foo( );

    void bar(string & s);

    那么下面的表达式将是非法的:

    bar(foo( ));

    bar("hello world");

    原因在于foo( )"hello world"串都会产生一个临时对象,而在C++中,这些临时对象都是const类型的。因此上面的表达式就是试图将一个const类型的对象转换为非const类型,这是非法的。

    引用型参数应该在能被定义为const的情况下,尽量定义为const

     

    6. “引用”与多态的关系

    引用是除指针外另一个可以产生多态效果的手段。这意味着,一个基类的引用可以指向它的派生类实例。

     

    14. i nclude<file.h> i nclude "file.h"的区别?

    答:前者是从Standard Library的路径寻找和引用file.h,而后者是从当前工作路径搜寻并引用file.h

     

    17.面向对象的三个基本特征,并简单叙述之?

    1. 封装:将客观事物抽象成类,每个类对自身的数据和方法实行protection(private, protected,public)

    2. 继承:广义的继承有三种实现形式:实现继承(指使用基类的属性和方法而无需额外编码的能力)、可视继承(子窗体使用父窗体的外观和实现代码)、接口继承(仅使用属性和方法,实现滞后到子类实现)。前两种(类继承)和后一种(对象组合=>接口继承以及纯虚函数)构成了功能复用的两种方式。

    3. 多态:是将父对象设置成为和一个或更多的他的子对象相等的技术,赋值之后,父对象就可以根据当前赋值给它的子对象的特性以不同的方式运作。简单的说,就是一句话:允许将子类类型的指针赋值给父类类型的指针

     

    19. 多态的作用?

    主要是两个:1. 隐藏实现细节,使得代码能够模块化;扩展代码模块,实现代码重用;2. 接口重用:为了类在继承和派生的时候,保证使用家族中任一类的实例的某一属性时的正确调用。

     

    21. New delete malloc free 的联系与区别?

    答案:都是在堆(heap)上进行动态的内存操作。用malloc函数需要指定内存分配的字节数并且不能初始化对象,new 会自动调用对象的构造函数。delete 会调用对象的destructor,而free 不会调用对象的destructor.

     

    24. C++是不是类型安全的?

    答案:不是。两个不同类型的指针之间可以强制转换(用reinterpret cast)C#是类型安全的。

     

    27.struct class 的区别

    答案:struct 的成员默认是公有的,而类的成员默认是私有的。struct class 在其他方面是功能相当的。

     

    32.请说出const#define 相比,有何优点

    答案:1 const 常量有数据类型,而宏常量没有数据类型。编译器可以对前者进行类型安全检查。而对后者只进行字符替换,没有类型安全检查,并且在字符替换可能会产生意料不到的错误。

    2 有些集成化的调试工具可以对const 常量进行调试,但是不能对宏常量进行调试。

     

    33.简述数组与指针的区别?

    数组要么在静态存储区被创建(如全局数组),要么在栈上被创建。指针可以随时指向任意类型的内存块

     

    34.类成员函数的重载、覆盖和隐藏区别?

    答案:

    a.成员函数被重载的特征:

    1)相同的范围(在同一个类中);

    2)函数名字相同;

    3)参数不同;

    4virtual 关键字可有可无。

    b.覆盖是指派生类函数覆盖基类函数,特征是:

    1)不同的范围(分别位于派生类与基类);

    2)函数名字相同;

    3)参数相同;

    4)基类函数必须有virtual 关键字。

    c.“隐藏”是指派生类的函数屏蔽了与其同名的基类函数,规则如下:

    1)如果派生类的函数与基类的函数同名,但是参数不同。此时,不论有无virtual关键字,基类的函数将被隐藏(注意别与重载混淆)。

    2)如果派生类的函数与基类的函数同名,并且参数也相同,但是基类函数没有virtual 关键字。此时,基类的函数被隐藏(注意别与覆盖混淆

     

    35. There are two int variables: a and b, don’t use “if”, “? :”, “switch”or other judgement statements, find out the biggest one of the two numbers.

    答案:( ( a + b ) + abs( a - b ) ) / 2

     

    37. main 主函数执行完毕后,是否可能会再执行一段代码,给出说明?

    答案:可以,可以用_onexit 注册一个函数,它会在main 之后执行int fn1(void), fn2(void);

    void main( void )

    {

    String str("zhanglin");

    _onexit( fn1 );

    _onexit( fn2 );

     

     

    printf( "This is executed first.n" );

     

    }

    int fn1()

    {

    printf( "next.n" );

    return 0;

    }

    int fn2()

    {

    printf( "executed " );

    return 0;

    }

     

    38. 如何判断一段程序是由C 编译程序还是由C++编译程序编译的?

    答案:

    #ifdef __cplusplus

    cout<<"c++";

    #else

    cout<<"c";

    #endif

     

    44. 多重继承的内存分配问题:

    比如有class A : public class B, public class C {}

    那么A的内存结构大致是怎么样的?http://blog.sina.com.cn/s/blog_4b 3c1f950100mfau.html

     

    45.IP地址从整数到字符串,从字符串到整数的转换

    IP地址字符串转为整数时,如果按照网络字节序转化的话,转换后的整数并不是从小到大排序的。以下两个函数可以实现IP地址转换后的整数从小到大排序。当然255.255.255.255是 -1 .

    sscanf 解释:http://baike.baidu.com/view/1364018.htm

    //IP整形数转为字符串,默认不按照网络字节序。
    //bNetOrder = TRUE时类似于调用winsock函数转换
    CString IPULongToCString(ULONG iIP, BOOL bNetOrder)
    {
     CString strIP;
     
     BYTE* pIP = (BYTE*)&iIP;
     if( bNetOrder )
     {
      strIP.Format("%d.%d.%d.%d", pIP[0], pIP[1], pIP[2], pIP[3]);
     }
     else
     {
      strIP.Format("%d.%d.%d.%d", pIP[3], pIP[2], pIP[1], pIP[0]);
     }

     return strIP;
     
    }


    ULONG IPCStringToULong(CString strIP, BOOL bNetOrder)
    {
     if(strIP.IsEmpty())
      return -1;
     
     ULONG ret;
     int pTmp[4];
     BYTE* pDst = (BYTE*)&ret;
     
     if(bNetOrder)
      sscanf((LPTSTR)(LPCTSTR)strIP, "%d.%d.%d.%d", &pTmp[0], &pTmp[1], &pTmp[2], &pTmp[3]);
     else
      sscanf((LPTSTR)(LPCTSTR)strIP, "%d.%d.%d.%d", &pTmp[3], &pTmp[2], &pTmp[1], &pTmp[0]);
     
     for(int i=0; i<4; i++)
      pDst[i] = (BYTE)pTmp[i];
     ret = (ULONG)*pDst;

     return ret;
    }

     

    46.为什么不能在析构函数中抛出异常?

    1、构造函数可以抛出异常。

    2、c++标准指明析构函数不能、也不应该抛出异常。

    more effective c++关于第2点提出两点理由:

    1)如果析构函数抛出异常,则异常点之后的程序不会执行,如果析构函数在异常点之后执行了某些必要的动作比如释放某些资源,则这些动作不会执行,会造成诸如资源泄漏的问题。

    2)通常异常发生时,c++的机制会调用已经构造对象的析构函数来释放资源,此时若析构函数本身也抛出异常,则前一个异常尚未处理,又有新的异常,会造成程序崩溃的问题。

    解决办法:

    1)永远不要在析构函数抛出异常。

    2)通常第一点有时候不能保证。可以采取如下的方法:

    ~ClassName()

    {

      try{

          do_something();

      }

      catch(){  //这里可以什么都不做,只是保证catch块的程序抛出的异常不会被扔出析构函数之外。

       }

    }

     

    4.swith的参数

    switch中的整形表达式的值必须是int兼容的类型,既可以是byte、short、char和int,以及enum类型。不可以使用浮点型float、double、long,并且各case子句中的c1, c2,...,是int型或字符型常量。

     

    1、一个学生的信息是:姓名,学号,性别,年龄等信息,用一个链表,把这些学生信息连在一起, 给出一个age, 在些链表中删除学生年龄等于age的学生信息。

    程序代码

    i nclude "stdio.h"

    i nclude "conio.h"

    struct stu{

    char name[20];

    char sex;

    int no;

    int age;

    struct stu * next;

    }*linklist;

    struct stu *creatlist(int n)

    {

    int i;

    //h为头结点,p为前一结点,s为当前结点

    struct stu *h,*p,*s;

    h = (struct stu *)malloc(sizeof(struct stu));

    h->next = NULL;

    p=h;

    for(i=0;i<n;i++)

    {

    s = (struct stu *)malloc(sizeof(struct stu));

    p->next = s;

    printf("Please input the information of the student: name sex no age n");

    scanf("%s %c %d %d",s->name,&s->sex,&s->no,&s->age);

    s->next = NULL;

    }

    }

     

    10. 嵌入式系统经常具有要求程序员去访问某特定的内存位置的特点。在某工程中,要求设置一绝对地址为0x67a9的整型变量的值为0xaa66。编译器是一个纯粹的ANSI编译器。写代码去完成这一任务。

    这一问题测试你是否知道为了访问一绝对地址把一个整型数强制转换(typecast)为一指针是合法的。这一问题的实现方式随着个人风格不同而不同。典型的类似代码如下:

    int *ptr;

    ptr = (int *)0x67a9;

    *ptr = 0xaa55;

     

    11. 中断是嵌入式系统中重要的组成部分,这导致了很多编译开发商提供一种扩展—让标准C支持中断。具代表事实是,产生了一个新的关键字__interrupt。下面的代码就使用了__interrupt关键字去定义了一个中断服务子程序(ISR),请评论一下这段代码的。

    __interrupt double compute_area (double radius)

    {

    double area = PI * radius * radius;

    printf(" Area = %f", area);

    return area;

    }

    这个函数有太多的错误了,以至让人不知从何说起了:

    1). ISR 不能返回一个值。如果你不懂这个,那么你不会被雇用的。

    2). ISR 不能传递参数。如果你没有看到这一点,你被雇用的机会等同第一项。

    3). 在许多的处理器/编译器中,浮点一般都是不可重入的。有些处理器/编译器需要让额处的寄存器入栈,有些处理器/编译器就是不允许在ISR中做浮点运算。此外,ISR应该是短而有效率的,在ISR中做浮点运算是不明智的。

    4). 与第三点一脉相承,printf()经常有重入和性能上的问题。如果你丢掉了第三和第四点,我不会太为难你的。不用说,如果你能得到后两点,那么你的被雇用前景越来越光明了。

     

    13. 评价下面的代码片断:

    unsigned int zero = 0;

    unsigned int compzero = 0xFFFF;

     

    对于一个int型不是16位的处理器为说,上面的代码是不正确的。应编写如下:

    unsigned int compzero = ~0;

     

    15. Typedef C语言中频繁用以声明一个已经存在的数据类型的同义字。也可以用预处理器做类似的事。例如,思考一下下面的例子:

    #define dPS struct s *

    typedef struct s * tPS;

    以上两种情况的意图都是要定义dPS tPS 作为一个指向结构s指针。哪种方法更好呢?(如果有的话)为什么?

    这是一个非常微妙的问题,任何人答对这个问题(正当的原因)是应当被恭喜的。答案是:typedef更好。思考下面的例子:

    dPS p1,p2;

    tPS p3,p4;

    第一个扩展为

    struct s * p1, p2;

    上面的代码定义p1为一个指向结构的指,p2为一个实际的结构,这也许不是你想要的。第二个例子正确地定义了p3 p4 两个指针。

     

    16. C语言同意一些令人震惊的结构,下面的结构是合法的吗,如果是它做些什么?

    int a = 5, b = 7, c;

    c = a+++b;

    这个问题将做为这个测验的一个愉快的结尾。不管你相不相信,上面的例子是完全合乎语法的。问题是编译器如何处理它?水平不高的编译作者实际上会争论这个问题,根据最处理原则,编译器应当能处理尽可能所有合法的用法。因此,上面的代码被处理成:

    c = a++ + b;

    因此, 这段代码持行后a = 6, b = 7, c = 12

    如果你知道答案,或猜出正确答案,做得好。如果你不知道答案,我也不把这个当作问题。我发现这个问题的最大好处是:这是一个关于代码编写风格,代码的可读性,代码的可修改性的好的话题

     

    1.多态类中的虚函数表是Compile-Time,还是Run-Time时建立的?

    虚拟函数表是在编译期就建立了,各个虚拟函数这时被组织成了一个虚拟函数的入口地址的数组.而对象的隐藏成员--虚拟函数表指针是在运行期--也就是构造函数被调用时进行初始化的,这是实现多态的关键.

     

    4.一个父类写了一个virtual 函数,如果子类覆盖它的函数不加virtual ,也能实现多态?

    关键字virtual只能出现在类定义中;

    在基类中声明为virtual的函数,在派生类中声明时不用再加上virtual关键字。

     

    5.纯虚函数

    定义:

    class a{

    virtual bool isok() = 0;

    };

    含有纯虚函数的类不能new自己,其被视为是抽象类;

    如何继承纯虚函数?

    class b:public a{

    bool isok();//继承

    bool isok()=0;//继承依然是纯虚函数

    virtual bool isok();//与a中的不同域 同名 覆盖

    };

     

    9.变量的声明和定义有什么区别?

    声明(declare)”是用于定义一个变量的类型;“定义(define)”是用于定义一个变量所占用的存储。

     

    17.分别实现itoaatoi

    1.//整数转换成字符串itoa函数的实现
    #include "stdafx.h"
    #include <iostream>
    using namespace std;
    void itoaTest(int num,char str[] )
    {
           int sign = num,i = 0,j = 0;
           char temp[11];
           if(sign<0)//判断是否是一个负数
           {
                  num = -num;
           };
           do
           {
                  temp[i] = num+'0';       
                  num/=10;
                  i++;
           }while(num>0);
           if(sign<0)
           {
                  temp[i++] = '-';
           }
           temp[i] = '';
           i--;
           while(i>=0)
           {
                  str[j] = temp[i];
                  j++;
                  i--;
           }
           str[j] = '';
    }
    2. //字符串转换成整数atoi函数的实现
    int atoiTest(char s[])
    {
           int i = 0,sum = 0,sign;    //输入的数前面可能还有空格或制表符应加判断
           while(' '==s[i]||'t'==s[i])
           {
                  i++;
           }
           sign = ('-'==s[i])?-1:1;
           if('-'==s[i]||'+'==s[i])
           {
                  i++;
           }
           while(s[i]!='')
           {
                  sum = s[i]-'0'+sum*10;
                  i++;
            
           return sign*sum;
    }

     

    18.编写一个函数,要求输入年月日时分秒,输出该年月日时分秒的下一秒

    #include <stdio.h>
    #include <iostream.h>
    #include <string.h>
    typedef   struct   {
          int   tm_year;
          short   tm_second;    
          short   tm_minute;  
          short   tm_hour;      
          short   tm_day;      
          short   tm_month;      
    }   time_all;

    bool   isRun(int   year)//判断是否为闰年
    {
    if(year%4==0)
    {
    if(year0==0)
    {
    if(year@0==0)   return   true;
    else   return   false;
    }
    else   return   true;
    }
    else   return   false;
    }

    int   getDays(int   month,int   year)//根据月份和年份算出当月的天数
    {
    int   a[12]={31,28,31,30,31,30,31,30,31,30,30,31};
    if(month!=2)   return   a[month-1];
    else  
    {
    if(isRun(year))   return   29;
    else                         return   28;
    }
    }

    int   addSecond(time_all&   nowTime)//增加一秒
    {
    //         time_all   tempStruct;
    // memcpy(&tempStruct,&nowTime,sizeof(time_all));
    if((nowTime.tm_second=++nowTime.tm_second`)!=0)
                      return   0;

    if((nowTime.tm_minute=(nowTime.tm_minute+1)`)!=0)
    return   0;

    if((nowTime.tm_hour=(nowTime.tm_hour+1)$)!=0)
    return   0;

    if(nowTime.tm_day!=getDays(nowTime.tm_month,nowTime.tm_year))
    {
    nowTime.tm_day++;
    return   0;
    }
    else   nowTime.tm_day=1;

    if(nowTime.tm_month!=12)
    {
    nowTime.tm_month++;
    return   0;
    }
    else   nowTime.tm_month=1;

    nowTime.tm_year++;
    return   0;
    }
           
    void   main()
    {
    time_all   timeNow;
    cout < < "输入时间   依次顺序为年、月、日、时、分、妙 " < <endl;
    cin> > timeNow.tm_year> > timeNow.tm_month> > timeNow.tm_day> > timeNow.tm_hour> > timeNow.tm_minute> > timeNow.tm_second;
    addSecond(timeNow);
            cout < <timeNow.tm_year < < "年 " < <timeNow.tm_month < < "月 " < <timeNow.tm_day < < "日 " < <timeNow.tm_hour < < "时 " < <timeNow.tm_minute < < "分 " < <timeNow.tm_second < < "秒 ";
    }

     

     

    19. 假设有一个没有头指针的单链表。一个指针指向此单链表中间的一个节点(非第一个节点,也非最后一个节点)。请将该节点从单链表中删除。

    解答:

            典型的“狸猫换太子”,若要删除该节点,正常情况下,应该要知道该节点的前面节点的指针,但是由于单链表中没有头结点,所以无法追溯到该节点前面的那个节点,因此,这里采用了“移花接木”的方法。设该节点为B,下一个节点为C。那么,首先将B节点的内容替换为C节点的内容,然后,将C节点删除,这样就达到了我们的目的。代码如下:

    pcur->next = pnext->next;

    pcur->data = pnext->date;

    delete pnext;

     

    代码:

    void DeleteListNode(node* pCurrent)  
         
     assert(pCurrent != NULL);      
     node* pNext = pCurrent -> next;     
     if (pNext == NULL)      
      pCurrent = NULL;    
     else  
         
      pCurrent -> next = pNext -> next;  
      pCurrent -> data = pNext -> data;   
      delete pNext;     
     }
    }

     

    类似问题:

    http://hi.baidu.com/liangrt_fd/blog/item/4deb905028aa0c55d00906da.html

    1、 从无头单链表中删除节点问题:假设有一个没有头指针的单链表,一个指针p指向单链表中的一个节点(不是第一个,也不是最后一个),请将该节点删除掉。

    2、 向无头单链表中添加节点问题:假设有一个没有头指针的单链表,一个指针p指向单链表中的一个节点(不是第一个,也不是最后一个),请在该节点之前插入一个新的节点q

     

            由于链表是无头单向链表,所以我们无法由当前节点获得p的前一节点,而无论是删除当前节点还是向前面节点插入新节点都需要获得p的前一节点。在这里我们不妨换一下思路,对当前节点的后继结点进行操作,然后将前后节点的数据进行适当交换也可以得到相应效果。

     

    问题1解法:p后继结点p->next的数据拷贝到p,然后删除p->next,这样就达到了相同的效果,代码如下:

                                    ListNode* p_next = p->next;

                                    p->value=p_next->value;

                                    p->next=p_next->next;

                                    delete   p_next;

    问题2解法:p节点后添加q,然后交换pq的数据即可。

                                    q->next=p->next;

                                    p->next=q;

                                    swap(&p->value, &q->value);

      

    扩展问题:

            将一个单链表,在只遍历一遍的情况下,将单链表中的元素顺序反转过来。

    解答:

            我的想法是这样的,用三个指针进行遍历,在遍历的途中,进行逆置。

     

     

    20.样统计一个字符串中包括空格数在内的每个字符的出现次数

    核心代码:

    while ((c!='n')
    {
      if((c>='a'&&c<='z')||(c>='A'&&c<='Z')) a[tolower(c)-'a']++;//统计每个字符出现的次数,不分大小写
      else if(c==' ') a[26]++;
    }

     

     

    21.用递归和非递归方式,写函数char *reverse(char *str)将一个字符串反转

    方法一:

    char *p;
    int nTotalLen;

    char *reverse(char *str, int nlen)
    {
        if (nlen == 1)
        {
           memcpy(p+nlen-1, str, 1);
           memset(p+nTotalLen, 0, 1);
           return p;
        }
        else
       {
          memcpy(p+nlen-1, str, 1);
          reverse(str+1, nlen-1); 
       
    }

    void main()
    {
        char a[] = "abcdefghijklmnopqrstuvwxyz1223456789";
        nTotalLen = sizeof(a)-1;
        p = new char[nTotalLen];
        memset(p ,0, nTotalLen);
        char* aa = reverse(a, nTotalLen);
    }

     
    方法二
    char *reverse(char *str)
    {
          static int len = strlen(str);
          char ctemp;
          if( len > 1 )
          {
              ctemp = str[len-1];
              str[len-1] = str[0];
              str[0] = ctemp;
             len -= 2;
             reverse( ++str );
           }
           return ( str - 1 );
    }
     
    非递归:
    方法一:
    char *reverse(char *str)
    {
        ASSERT( str != NULL );
        size_t len strlen( str );
        for size_t 0; len 2; i++ )
        {
            char chTemp str[i];
            str[i] str[len 1];
            str[len 1] chTemp;
        }
        return str;
    }
    方法二
    char * reverse( char * pStr)
    {
         int nLen=strlen(pStr)-1;
         char tmp;
         int x;
         for(x=0; x<nLen; x++, nLen--)
         {
              //swap
              pStr[x]    ^=pStr[nLen];
              pStr[nLen] ^=pStr[x];
              pStr[x]    ^=pStr[nLen];
         }
         return pStr;
    }

    方法三
    char *reverse(char *lpStr)
    {
        char *lpHead = lpStr;
        char *lpTail = lpStr;
        while (*lpTail)
        lpTail++;

        for (--lpTail; lpHead < lpTail; lpHead++, lpTail--)
       {
            char chTmp = *lpHead;
            *lpHead = *lpTail;
            *lpTail = chTmp;
        }

        return lpStr;
    }

     

    22.二分法排序的c语言算法

    #include <stdio.h>
    int a[10]={21,56,43,12,3,99,56,23,2,12};
    main()
    {
        int i,j,k,low,high,mid,t; 
        for(i=k=1;i<sizeof a/sizeof a[0];i++)//起始认为第一个元素是有序的,high=low=k-1=0,所以k=1, 
       
            low=0; 
            high=k-1; 
            while(low<=high)////折半查找时,low与high相遇,则找到插入位置 
           
                mid=(low+high)/2; 
                if(a[mid]>=a[i])high=mid-1;///////元素比mid小,因此在low到mid-1范围内搜索位置 
                else low=mid+1; 
           
            if(high<i|| a[low]!=a[i]) /////////// 
           
                t=a[i]; 
                for(j=k-1;j>=low;j--) //////插入位置是low,所以low到high=k-1范围内的元素都要向后移动 
                    a[j+1]=a[j]; 
                a[low]=t; //////////////low被赋值为已经被覆盖掉的a[i] 
                k++; 
            
       
        for(j=0;j<k;j++) 
        printf("M",a[j]); 
        printf("n");
    }

     

     

  • 相关阅读:
    Android EditText 文本框实现搜索和清空效果
    Android学习笔记之打钩显示输入的密码
    Mysql limit offset
    Android SDK更新以及ADT更新出现问题的解决办法
    Android Broadcast Receiver 使用入门
    立即执行函数
    JS 原型 & 继承
    JS 对象
    chrome extension overview
    JS 修改元素
  • 原文地址:https://www.cnblogs.com/liuzhuqing/p/7480833.html
Copyright © 2011-2022 走看看