c++基础知识总结

来源:互联网 发布:美工刀片生锈了怎么办 编辑:程序博客网 时间:2024/06/17 16:58

C++关键字

1 startic(静态)变量的作用
a 函数体内部的静态变量在函数被调用过程中值保持不变(其值再下次调用时扔维持上次的值)
b 模块内部的静态全局变量可以被模块内部的函数访问,不能被模块外的其它函数访问
c 模块内的静态函数只可被这一模块内的其它函数调用,这个函数的使用范围被限制在声明它的模块内
d 类内静态成员变量认为是类的成员,由类的所有对象共享访问;(在没有类的对象时,也可以使用它);(static成员变量初始化在类外)。而静态成员函数也是为了类服务,是类的内部实现,无法访问非静态数据成员(只能访问静态成员变量),只能调用其它的静态成员函数。
//static全局变量和普通的全局变量区别在于static全局变量只初始化一次。static局部变量只被初始化一次,下一次运算是依据上一次的结果值。

2 const作用
常类型的变量或对象的值是不能被更新(只读)的。
a 定义const常量
b 进行类型检查,消除隐患,例如 void f(const int i){}
c 避免魔数,也方便的进行参数的调整和修改 int Max = 期望值
d 保护被修饰的东西被意外的修改,增加程序的健壮性
e 为函数重载提供参考
f 节省空间(和define相比)和提高程序效率(不需要存储和读内存操作)

3 switch语句中case结尾是否必须添加break
一般要在case后添加break语句,没有遇到关键字break,那么匹配的case值后的所有情况都会被执行。
switch(c)语句中 c不可以是float类型。

4 volatile的作用
与关键字const对应,说明这个变量可能会被意想不到的改变。一般在多线程中修饰被多个任务共享的变量。

5 ASSERT()是什么
断言,ASSERT()是宏,用来捕获非法输入。如果括号内部条件不满足就会终止运行。assert()是个函数,可以在release版本下使用。一般来说assert()只检验一个条件,如果同时两个条件,无法判断具体那个出了问题。

6 枚举变量的值如何计算
默认为前一个变量值加1,如果没有即是默认为0。可重复可单独赋值,例如 enum{a,b=5, c,d=4,e} 输出 0 5 6 4 5

7 char str[] =”abc”, char str1[]=”abc”str与str1不相等
str与str1是字符数组,有各自的存储区,指向的是各自存储区的首地址。而char* str = “abc”;char* str1 = “abc”; 相等。 str是字符指针,不分配存储区,abc以常量方式存在与常量区,str和是str1指向地址的首地址,相同。(&str和&str1是指针自己的地址,不相同)

8 new/delete与malloc/free的区别
1 new是运算符,malloc是函数
2 new可以自动计算需要分配内存的空间大小
3 new是类型安全的,int* a=new float会报错
4 new将调用构造函数,delete析构函数
5 new不需要库文件支持
需注意的是delete后,需要将指针指向置为空。

9 实现string函数体 (经典问题)

class mystring{public:    mystring(const char* str=nullptr);    mystring(const mystring& other);    ~mystring();    mystring& operator = (const mystring& other);private:    char* data; }mystring::~mystring(){    delete[] data;    data = nullptr; }mystring::mystring(const char* str){    if(str==nullptr)    {        data = new char[1];        data[0] = '\0';    }    else    {        data = new char[strlen(str)+1];        strcpy(data, str);    }   }mystring::mystring(const mystring& other){    data = new char[strlen(other.data)+1];  //可以直接访问类的成员变量...    strcpy(data, other.data);   }mystring& mystring::operator=(const mystring& other)//返回值是引用可以实现a=b=c的赋值\参数为常量引用可提高效率{    if(this==&other)        return *this;   //对象的引用    delete[] data;      //释放原先的内存    data = nullptr;    data = new char[strlen(other.data)+1];    strcpy(data, other.data);    return *this;}

10 c++中关键字explicit的作用
声明为explicit的构造函数不能在隐式转换中使用。

sizeof 和指针和预处理

1 strlen(“\0”)=? sizeof(“\0”)=?
strlen是函数,碰到第一个字符串结束符’\0’为止,停止计数(不包括\0),参数只能为char*,必须以’\0’结尾;sizeof是关键字,以字节形式给出操作数的存储大小,操作数可以是表达式或者类型名(sizeof(int))。大部分编译程序的sizeof都是在编译期计算的,strlen的大小是运行期确定的。可以这么说,sizeof只关心分配的空间大小,strlen只关心存储的数据内容。前者为0, 后者为2(常量字符串结尾有个\0占一个字节);
例:
char str[10] = “1234”; sizeof(str) = 10; strlen(str) = 4;
char* p = new char[10];sizeof(p) = 4;p是指针; strlen(p)=0因为没有初始化没有定义\0,所以为随机值;sizeof(*p)=1;
char* a =”123456”;strlen(a)=6;sizeof(a)=4;

2 对于结构体而言,为什么sizeof返回值一般大于期望值?
为了使CPU的性能达到最佳,编译器对struct结构进行了4的倍数(32位)8的倍数(64位)字节对齐;
例1:

struct test{    char x1;    short x2;    float x3;    char x4;}

编译器默认对struct进行边界对齐,x1占用和x2相同,2;x1+x2=4;正好是自然边界地址上;x3是结构所有成员中要求的最大边界单元;x4为4;一共12;
字节对齐准则:
a 结构体的总大小为最宽接本类型成员大小的整数倍

3 指针进行强制类型转换后与地址进行加法运算,结果是
例:

struct A{    long x1;    char* x2;    short int x3;    char x4;    short x5[5];}*p;

依据4字节对齐,可计算出sizeof(A)为4+4+2+2+12=24。p=0x1000000, p+0x200 = 0x1000000+0x200 乘24(指针加法);(Ulong)p +0x200 就是数值加法。(char*)p+0x200 = 0x1000000+0x200乘4;

4 指针和数组的区别
例:char a[] = “song”;char*b = “rain”;前者可通过下标操作改变值,后者不能改变值,因为指向了常量字符串;sizeof(b)=4;sizeof(a)=5;当数组作为函数的参数进行传递时,自动退化为同类型的指针。

5 指针和引用的区别
a 指针是一个存放变量地址的变量,自身值可以改变,指向地址的变量值也可以改变。引用是变量的小名,一开始要被初始化,自始至终依赖于同一个变量。指针指向的变量值是可以改变,指针本身可以置空,引用不可以为空。
b 参数传递时不同,传递的是引用,会影响实参。对指针的改变,只是局部变量,不影响实际参数的值。
c 对引用进行sizeof是所指向变量的大小,对指针sizeof就是指针本身的值。

6 指针与数字相加
char* p1; long*p2; p1地址0x000001 p1+5 = 0x000006;p2地址0x000001;p2+5 = p2+20;注意要转换成16进制0x000015;

7 空指针
free/delete后虽然把指针指向的内存释放掉了,但没有把指针本身释放掉,所以也要将指针设为nullptr;

8 用define声明一个常数,表示一年多少秒

#define SECOND_PER_YEAR (365*24*3600)UL

没有分号,末尾考虑溢出,告诉编译器为UL类型,加上括号防止表达式变了

9 定义宏来比较两个数大小,不能用>,<,if

#define check(a, b) (((a)-(b))==fabs((a)-(b)))?“greater”:"smaller"

接受浮点值

10 判断一个变量是有符号还是无符号
最高位置1

unsigned int A= 10;A = A|(1<<31);if(A>0)    cout<<“无符号”;else    cour<<“有符号”;

11 不使用sizeof,求int占用的字节数

#define mySizeof(Value) (char*)(&Value+1) - (char*)&Value

(char*)&Value返回Value的地址第一个字节,前者返回下一个地址的第一个字节

12 内联函数和普通函数的区别
普通函数被调用时,系统首先跳跃到函数的入口地址,执行函数体,执行完成后,再返回函数调用的地方,函数始终只有一个复制。内联函数省去了寻址过程,执行到内联函数时,函数展开。如果在N处调用了此内联函数,该函数就有N个代码段的复制。所以当函数体过大,编译器也会放弃内联方式。

位运算

1 最有效的x乘8
x<<3 CPU支持位运算、效率最高,同理x>>1表示x/2;
快速求取一个整数的7倍? (X<<3)-X (实际上测量2亿次计算,时间差别并不大,默认位运算快一些吧)

2 位操作实现求两个数的平均值
(x&y)+((x^y)>>1)
x&y代表二者都为1的部分,x^y代表,x=0,y=1和x=1,y=0的部分再/2;
如何利用位运算求数x的绝对值?
int y = x>>31; (x^y)-y;计算机中负数以补码形式存在(举例,-1,表示为0xfffffff, 求负数的补码是 负数的绝对值 取反+1)。求负数的绝对值是按位取反,末位加1;;如果x是负数,y=-1;x是正数,y=0;

3 整形数的二进制中1的个数

int f(int x){    int cnt=0;    while(x)    {        cnt++;        x = x&(x-1);     //x&x-1 每次x中去掉最后一个1,有几个1就循环几次    }}

4 考虑n位二进制数,有多少个数不存在两个相邻的1
当没什么实际操作时,不如找规律。n=1,2个。n=2,3个;n=3,5个。当第n位为0时,有a(n-1);当第n位为1时,有a(n-2),所以最终a(n) = a(n-1) + a(n-2);递归的重要思想,假设已完成。
斐波那契数列的递归写法:

long long Fibonacci(unsigned int n){    if(n==1)        return 2;    if(n==2)        return 3;    return Fibonacci(n-1) + Fibonacci(n-2);}

一般用非递归写法:

long long Fibonacci(unsigned int n){    if (n == 1)        return 2;    if (n == 2)        return 3;    long long num1 = 2, num2 = 3, numall=0;    for (unsigned int i=3;i<=n;++i)    {        numall = num1 + num2;        num1 = num2;        num2 = numall;    }    return numall;}

5 剑指offer题目—一个整形数组里除了两个数字之外,其它的数字都出现了两次,请找出这两个只出现一次的数字。要求时间复杂度o(n),空间复杂度1
一个数与自身异或结果是0;

unsigned int FindFirstBitIs1(int num){    unsigned int index = 0;    while (((num & 1)==0) && (index<32))    {        num = num >> 1;        ++index;    }    return index;}bool isBit1(int num, unsigned int bits){       num = num >> bits;    return (num & 1);}void FindNumAppearOnce(int data[], int length, int* num1, int* num2){    if (data == nullptr || length < 2)        return;    int result = 0;    unsigned int pos = 0;    for (int i = 0; i < length; ++i)        result ^= data[i];    pos = FindFirstBitIs1(result);    //找到第一个位置为1*num1 = *num2 = 0;    for (int j = 0; j < length; ++j)    {        if (isBit1(data[j], pos))     //第n位是否为1             *num1 ^= data[j];        else            *num2 ^= data[j];    }}

6 写一个函数求两个整数之和 不能用+、-、*、/

int Add(int num1, int num2){    int sum, carry;    while (num2 != 0)                //num2为0意味着没有进位产生    {        sum = num1^num2;             //各位想加, 不考虑进位        carry = (num1&num2) << 1;    //考虑进位,即相与再左移1位        num1 = sum;                  //重复前面操作        num2 = carry;    }    return num1;}

函数 AND 数组 AND 变量

1 重载与覆盖的区别
重载:通过赋予函数不同的参数表(参数类型,个数,顺序不同),对同名函数进行修饰。同名函数构成了不同的函数(对编译器来说是这样的)。对于重载函数的调用,在编译期间已经确定,是静态的。注意:重载函数不关系函数返回值类型,也就是说返回值类型不同不能构成重载。

覆盖:派生类中存在重新定义基类的函数,其函数名、参数列、返回值类型必须同基类被覆盖的函数严格一致。与多态相关。属于动态调用,无法在编译期间确定的,也就是运行期绑定。

区别:
a 覆盖是子类和父类之间,垂直关系。重载是一个类中方法之间的关系,水平关系。
b 覆盖参数列表相同,重载要求不同

扩展:
隐藏是指派生类的函数屏蔽了与其同名的基类函数。
a 如果派生类的函数与基类的函数同名,但参数不同,无论是否有virtual关键字,都被隐藏。
b 如果派生类的函数与基类函数同名,但参数相同,如果基类函数没有virtual关键字,被隐藏。
在调用一个类的成员函数时, 编译器会沿着类的继承链逐级的向上查找函数的定义。隐藏情况是派生类把基类的同名函数隐藏了。

2 a是数组, (int*)(&a+1)表示什么意思

int a[5] = {1,2,3,4,5};int* p = (int*)(&a+1);   //此时p指向5的下一个位置cout<<*(a+1);            // 2cout<<*(p-1);            // 5int b[100];cout<<sizeof(b);         //400 数组的大小cout<<sizeof(&b);        //400 也是数组的大小

3 不使用流程控制语句,如何打印出1~1000的整数
静态变量与构造函数结合

struct print1{    static int a;    print1() {cout << a++<<' ';}};int print1::a = 1;int main(){       print1 test[1000];    system("pause");}

扩展:1=2+2+…+n 不能使用乘除法,for,while,if
递归实现

int f(int n){    int i=1;    (n>1) && (i=f(n-1)+n);    return i;}

4 全局变量和静态变量有什么异同
相同点:都保留在静态存储区,生命期与程序生命期相同
不同点在于全局变量具有全局作用域,静态变量具有文件作用域。
静态变量包括静态全局变量和静态局部变量。静态局部变量具有局部作用域,只被初始化1次,自从第一个被初始化直到程序运行结束一直存在。它和全局变量的区别在于全局变量对所有函数都是可见的,静态局部变量只对定义自己的函数体始终可见。静态全局变量也具有全局作用域,如果程序包含多个文件的话,它作用于定义它的文件里,不能作用到其它文件里。全局变量只需在一个文件里定义,可作用于所有的源文件,其它不包含全局变量定义的源文件需要用extern关键字再次声明这个全局变量。(函数体内部的静态局部变量只会初始化一次,再次调用此函数时会跳过初始化那句语句)。

5 局部变量屏蔽全局变量
局部变量可以与全局变量重名,但会屏蔽全局变量。

int i =1;int main(){    int i = i;    cout<<i;         //编译可以通过,main内部的i与外部的i无关,且定义那一刻起i就是可见的}

6 复杂声明
a 一个指向有10个整数数组的指针 int (*a)[10]
b 一个指向函数的指针,一个整形参数返回整形 int(*f)(int);
c 一个有10个指针的数组,该指针指向一个函数,有一个整形参数返回一个整形数 int (*f[10])(int)
注:int(*)(int) 强制转换为指向函数的指针

7 不使用其它变量,交换两个变量的值
一个数与本身异或是0

int a=3;int b =5;a = a^b;b = a^b;a = a^b;

字符串

1 自行编写strcpy()函数
copy和in的内存区域不能重叠,copy要有足够的空间容纳in。返回值的作用在于在已有的基础上添加功能,支持链式表达。int length = strlen(strcpy(dest, “Hello”));

char* strcpy(char* copy, char* in){    assert(copy != nullptr && in != nullptr);    //条件不满足,将结束程序    char* out = copy;    while ((*out++ = *in++) != '\0')             //如果考虑内存重叠 此行和下一行将替换为 memcpy(copy, in, strlen(in)+1);        ;    return out;}

自定义的memcpy

char* memcpy(char* strDest, char* strSrc, int length){    assert(strDest != nullptr && strSrc != nullptr);    char* address = strDest;    if (strDest >= strSrc && strDest <= strSrc + length - 1)          //目标位置首字母位于源中间  dest="bcd", src="abc";由低位置向高位置复制    {         strDest = strDest + length - 1;        strSrc = strSrc + length - 1;        while (length--)            *strDest-- = *strSrc--;    }    else    {        while (length--)            *strDest++ = *strSrc++;    }    return address;}

2 将字符串转化为数字

bool isValid = true;          //设为全局变量两个函数都需要long long stoiCore(char* str, bool isMinus){    long long num = 0;                //long long类型才能判断是否出界    while (*str!='\0')    {        if (!isdigit(*str))          //不为数字            break;          else        {            int flag = isMinus ? -1 : 1;            num = num * 10 + flag * (*str - '0');            if ((!isMinus && num > 0x7fffffff) || (isMinus && num < (int)0x80000000))         //超过最大值或者最小值                break;            str++;        }    }    if (*str == '\0')        isValid = true;    else        num = 0;    return num;}int stoi1(char* str){    isValid = false;    long long num = 0;    if (str != nullptr && *str != '\0')      //指针不为空并且不为'\0'字符    {        bool isMinus = false;        if (*str == '+')            str++;        else if (*str == '-')        {            isMinus = true;            str++;        }        if (*str != '\0')                     //不为'+','-'            num = stoiCore(str, isMinus);    }    return (int)num;}

3 把char类型字符串右移k位,abc移动1位->cab

void LoopMove(char* str, int k){    int n = strlen(str) - k;    char temp[1000];    strcpy(temp, str+n);    strcpy(temp+k, str);    *(temp+strlen(str))=='\0';       //最后一位置空    strcpy(str, temp);}

面向对象 AND 虚函数

1 什么是虚函数
指向基类的指针根据不同的类对象调用其相应的函数,这个函数就是虚函数。用virtual修饰函数名。定以了虚函数后,可以在基类的派生类中对虚函数重新进行定义,重新定义的函数应与虚函数具有相同的形参个数和形参类型(顺序也要一致),以实现统一的接口。需注意一下几方面:
a 只需要在声明函数的类中使用关键字virtual将函数声明为虚函数,定义函数时不需要关键字
b 当将基类的某一成员函数声明为虚函数,派生类中的同名函数自动成为虚函数
c 如果声明了某个成员函数为虚函数后,该类中不能出现于这个成员函数同名并且返回值、参数个数、类型都相同的非虚函数
d非类的成员函数不能定义为虚函数,全局函数以及类的成员函数中的静态成员函数和构造函数不能定义为虚函数。基类的析构函数应定义为虚函数,多态时不会造成内存泄露,如果未声明,基类指针指向派生类时,delete指针不调用派生类的析构函数(只调用基类的);否则,会先调用派生类的析构函数再调用基类的析构函数。
f 普通派生类对象,先调用基类构造函数,再调用派生类构造。
虚函数的实现:
通过一张虚函数表实现的(函数指针)

2 c++如果实现多态
通过虚函数实现的。虚函数的本质就是通过基类访问派生类定义的函数。每一个含有虚函数的类,其实例对象内部都有一个虚函数表指针。

3 空

4 什么是友元
为了使非成员函数可以访问类的私有成员,作用在于提高了程序的运行效率,但破坏了类的封装性和隐藏性。
需要注意以下几点:
a 必须在类的说明中说明友元函数,以关键字friend开头。可以出现在类的任何地方,包括public和private.
b 友元函数不是类的成员函数,所以实现时不用加::
c 友元函数不能直接访问类成员,只能访问对象成员
d 友元函数可以访问对象的私有成员
e 调用友元函数时,实际参数需要指出要访问的对象
f 类与类之间的友元关系不能继承

5 c++设计实现一个不能被继承的类

template<typename T>class Makefinal{    friend T;private:    Makefinal() {}    ~Makefinal() {}};class FinalClass2 :virtual public Makefinal<FinalClass2>{public:    FinalClass2() {}    ~FinalClass2() {}};

6 C++空类中默认产生哪些成员函数?
默认构造函数,复制构造函数,析构函数,赋值运算符重载函数,取址运算符重载函数,const取址运算符重载函数

class Empty{public:    Empty();    Empty(const Empty&);    ~Empty();    Empty& operator =(const Empty&);    Empty* operator&();    const Empty* operator&() const;};

7 设置类的构造函数的可见性
将类的构造函数/析构函数声明为protected,将类的子类构造函数声明为public

8 public继承,私有继承,保护继承的区别是?
A:B
a 公有继承
基类成员(A)对其对象的可见性为,公有成员可见,其它成员不可见。
派生类(B)基类的公有成员和保护成员可见。基类的私有成员依然是私有的。
b 保护继承
派生类(B)基类的公有成员和保护成员可见。基类的公有成员和保护成员都作为B的保护成员,并不能被B的子类访问。基类的私有成员不可见。
派生类对象(b)基类的所有成员不可见。
c 私有继承
派生类(B)公有成员和保护成员可见,基类的公有和保护都作为派生类的私有成员,不能被派生类的子类访问。派生类对象(b)基类的所有成员不可见。

9 c++提供默认参数的函数吗
c++可以给函数定义默认参数值。在函数调用时没有指定与形参相对应的实参时,就自动使用默认参数。需要注意几点:
a 如果声明中给出默认值,函数定义中不允许再给出默认值。
b 形参中的默认值为从右向左定义,即void f(int a, int b=1, int c=2)正确
void f(int a=1, int b, int c=2)错误
c 函数调用时,默认从左往右调用。f(6,7) a=6,b=7,c=2;
d 默认值可以是全局变量,全局常量,甚至是一个函数,不可以为局部变量。

编程技巧 有点意思

1 不使用if/?:/switch 判断两个int型变量的最大值,最小值

int max = ((a+b)+abs(a-b))/2;int min = ((a+b)-abs(a-b))/2;

2 最简单病毒

while(1)    int* virus = new int[100000];

3 判断x是否为2的若干次幂
(i&(i-1))?false:true

4 短路求值
(条件1 && 条件2)如果条件1假,条件2不执行
(条件1 || 条件2) 条件1 真,条件2不执行

补充

1 heap与stack的区别
并不是数据结构中的堆和栈

a. 栈的空间由系统自动分配和释放,堆上的空间由程序员手动分配/释放(new/delete)。
b.栈的空间有限,堆是很大的自由存储区。
c.程序编译器对变量和函数分配内存都在栈上进行,且程序运行过程中函数调用时参数的传递也在栈上进行。

原创粉丝点击