这篇文章汇总了 C++ 基本语法, 面向对象各种概念与易错点, 总结讲述了 C++ 面试常见问题. 分享给大家供大家参考, 具体如下:
1. 继承方式
public 父类的访问级别不变
protected 父类的 public 成员在派生类编程 protected, 其余的不变
private 父类的所有成员变成 private
- #include <iostream>
- using namespace std;
- class base
- {
- public:
- void printa()
- { cout <<"base"<<endl; }
- protected:
- void printhello()
- { cout <<"helo"<< endl; }
- private:
- void printnohello()
- { cout <<"no hello"<< endl; }
- };
- class derived : public base
- {
- public:
- void printb() { printhello(); }
- // void printc() { printnohello(); } //printnohello 是父类的私有函数, 不可访问
- };
- int main()
- {
- base a;
- a.printa();
- //a.printhello(); //printhello 是类 derived 的 protected 函数, 不可访问.
- }
2. sizeof 和 strlen 的区别
1 sizeof 是一个操作符, strlen 是库函数.
2 sizeof 的参数可以是数据的类型, 也可以是变量, 而 strlen 只能以结尾为'\ 0'的字符串作参数.
3 编译器在编译时就计算出了 sizeof 的结果. 而 strlen 函数必须在运行时才能计算出来. 并且 sizeof 计算的是数据类型占内存的大小, 而 strlen 计算的是字符串实际的长度.
4 数组做 sizeof 的参数不退化, 传递给 strlen 就退化为指针了.
- #include
- #include <cstdlib>
- #include <cstring>
- using namespace std;
- int main()
- {
- int a[] = {
- 1, 2, 3, 4, 5
- };
- cout <<sizeof(a) << endl; //20
- // cout << strlen(a) << endl;
- char b[] = {
- 'a', 'b'
- };
- cout << strlen(b) << endl; //6
- cout << sizeof(b) << endl; //2
- }
3. C 中的 malloc 和 C++ 中的 new 有什么区别
new,delete 是操作符, 可以重载, 只能在 C++ 中使用.
malloc,free 是函数, 可以覆盖, C,C++ 中都可以使用.
new 可以调用对象的构造函数, 对应的 delete 调用相应的析构函数.
malloc 仅仅分配内存, free 仅仅回收内存, 并不执行构造和析构函数
new,delete 返回的是某种数据类型指针, malloc,free 返回的是 void 指针.
注意: malloc 申请的内存空间要用 free 释放, 而 new 申请的内存空间要用 delete 释放, 不要混用.
因为两者实现的机理不同.
如果有想要学习 C/C++ 的小伙伴, 可以关注小编[C/C++ 企鹅群 374890319] ,wx 公众号: CPP_cx 小编也有 5 年编程经验了, 免费送一套比较系统的资料, 教程和工作经验, 就当是福利吧! 21 天速成加 QQ 群: 374890319
4.1. c/c++ 中 static
要理解 static, 就必须要先理解另一个与之相对的关键字 auto, 其实我们通常声明的不用 static 修饰的变量, 都是 auto 的, 因为它是默认的. auto 的含义是由程序自动控制变量的生存周期, 通常指的就是变量在进入其作用域的时候被分配, 离开其作用域的时候被释放; 而 static 就是不 auto, 变量在程序初始化时被分配, 直到程序退出前才被释放; 也就是 static 是按照程序的生命周期来分配释放变量的, 而不是变量自己的生命周期; 所以, 像这样的例子:
- void func()
- {
- int a;
- static int b;
- }
每一次调用该函数, 变量 a 都是新的, 因为它是在进入函数体的时候被分配, 退出函数体的时候被释放, 所以多个线程调用该函数, 都会拥有各自独立的变量 a, 因为它总是要被重新分配的; 而变量 b 不管你是否使用该函数, 在程序初始化时就被分配的了, 或者在第一次执行到它的声明的时候分配(不同的编译器可能不同), 所以多个线程调用该函数的时候, 总是访问同一个变量 b, 这也是在多线程编程中必须注意的!
static 的全部用法:
1.类的静态成员:
- class A
- {
- private:
- static int s_value;
- };
在 cpp 中必须对它进行初始化:
int A::s_value = 0; // 注意, 这里没有 static 的修饰!
类的静态成员是该类所有实例的共用成员, 也就是在该类的范畴内是个全局变量, 也可以理解为是一个名为 A::s_value 的全局变量, 只不过它是带有类安全属性的; 道理很简单, 因为它是在程序初始化的时候分配的, 所以只分配一次, 所以就是共用的;
类的静态成员必须初始化, 道理也是一样的, 因为它是在程序初始化的时候分配的, 所以必须有初始化, 类中只是声明, 在 cpp 中才是初始化, 可以在初始化的代码上放个断点, 在程序执行 main 的第一条语句之前就会先走到那; 如果你的静态成员是个类, 那么就会调用到它的构造函数;
2.类的静态函数:
- class A
- {
- private:
- static void func(int value);
- };
实现的时候也不需要 static 的修饰, 因为 static 是声明性关键字; 类的静态函数是在该类的范畴内的全局函数, 不能访问类的私有成员, 只能访问类的静态成员, 不需要类的实例即可调用; 实际上, 它就是增加了类的访问权限的全局函数: void A::fun(int);
静态成员函数可以继承和覆盖, 但无法是虚函数;
3.只在 cpp 内有效的全局变量:
在 cpp 文件的全局范围内声明:
static int g_value = 0;
这个变量的含义是在该 cpp 内有效, 但是其他的 cpp 文件不能访问这个变量; 如果有两个 cpp 文件声明了同名的全局静态变量, 那么他们实际上是独立的两个变量;
如果不使用 static 声明全局变量:
int g_value = 0;
那么将无法保证这个变量不被别的 cpp 共享, 也无法保证一定能被别的 cpp 共享, 因为要让多个 cpp 共享一个全局变量, 应将它声明为 extern(外部)的; 也有可能编译会报告变量被重复定义; 总之不建议这样的写法, 不明确这个全局变量的用法;
如果在一个头文件中声明:
static int g_vaule = 0;
那么会为每个包含该头文件的 cpp 都创建一个全局变量, 但他们都是独立的; 所以也不建议这样的写法, 一样不明确需要怎样使用这个变量, 因为只是创建了一组同名而不同作用域的变量;
这里顺便说一下如何声明所有 cpp 可共享的全局变量, 在头文件里声明为 extern 的:
extern int g_value; // 注意, 不要初始化值!
然后在其中任何一个包含该头文件的 cpp 中初始化 (一次) 就好:
int g_value = 0; // 初始化一样不要 extern 修饰, 因为 extern 也是声明性关键字;
然后所有包含该头文件的 cpp 文件都可以用 g_value 这个名字访问相同的一个变量;
如果有想要学习 C/C++ 的小伙伴, 可以关注小编[C/C++ 企鹅群 374890319] ,wx 公众号: CPP_cx 小编也有 5 年编程经验了, 免费送一套比较系统的资料, 教程和工作经验, 就当是福利吧! 21 天速成加 QQ 群: 374890319
4.2 C 中 static 有什么作用
1 隐藏. 当我们同时编译多个文件时, 所有未加 static 前缀的全局变量和函数都具有全局可见性, 故使用 static 在不同的文件中定义同名函数和同名变量, 而不必担心命名冲突.
2 保持变量内容的持久. 存储在静态数据区的变量会在程序刚开始运行时就完成初始化, 也是唯一的一次初始化. 共有两种变量存储在静态存储区: 全局变量和 static 变量.
3 默认初始化为 0. 其实全局变量也具备这一属性, 因为全局变量也存储在静态数据区. 在静态数据区, 内存中所有的字节默认值都是 0*00, 某些时候这一特点可以减少程序员的工作量.
5. 简述 C\C++ 程序编译的内存情况分配
C,C++ 中内存分配方式可以分为三种:
(1)从静态存储区域分配: 内存在程序编译时就已经分配好, 这块内存在程序的整个运行期间都存在. 速度快, 不容易出错, 因为有系统会善后. 例如全局变量, static 变量等.
(2)在栈上分配: 在执行函数时, 函数内局部变量的存储单元都在栈上创建, 函数执行结束时这些存储单元自动被释放. 栈内存分配运算内置于处理器的指令集中, 效率很高, 但是分配的内存容量有限.
(3)从堆上分配: 即动态内存分配. 程序在运行的时候用 malloc 或 new 申请任意大小的内存, 程序员自己负责在何时用 free 或 delete 释放内存. 动态内存的生存期由程序员决定, 使用非常灵活. 如果在堆上分配了空间, 就有责任回收它, 否则运行的程序会出现内存泄漏, 另外频繁地分配和释放不同大小的堆空间将会产生堆内碎块.
一个 C,C++ 程序编译时内存分为 5 大存储区: 堆区, 栈区, 全局区, 文字常量区, 程序代码区.
6. 面向对象的三大特征
1 封装, 也就是把客观事物封装成抽象的类, 并且类可以把自己的数据和方法只让可信的类或者对象操作, 对不可信的进行信息隐藏.
2 继承, 是指这样一种能力: 它可以使用现有类的所有功能, 并在无需重新编写原来的类的情况下对这些功能进行扩展. 通过继承创建的新类称为 "子类" 或 "派生类". 继承的过程, 就是从一般到特殊的过程. 要实现继承, 可以通过 "继承" 和 "组合" 来实现.
3 多态, 简单的说, 就是一句话: 允许将指向子类类型的指针赋值给父类类型的指针. 实现多态, 有二种方式, 覆盖, 重载.
覆盖, 是指子类重新定义父类的虚函数的做法.
重载, 是指允许存在多个同名函数, 而这些函数的参数表不同(或许参数个数不同, 或许参数类型不同, 或许两者都不同).
总结: 作用
1 封装可以隐藏实现细节, 使得代码模块化
2 继承可以扩展已存在的代码模块(类); 它们的目的都是为了 -- 代码重用
3 多态则是为了实现另一个目的 -- 接口重用! 多态的作用, 就是为了类在继承和派生的时候, 保证使用 "家谱" 中任一类的实例的某一属性时的正确调用.
7. 简述多态的实现原理
编译器发现一个类中有虚函数, 便会立即为此类生成虚函数表 vtable. 虚函数表的各表项为指向对应虚函数的指针. 编译器还会在此类中隐含插入一个指针 vptr 指向虚函数表. 调用此类的构造函数时, 在类的构造函数中, 编译器会隐含执行 vptr 与 vtable 的关联代码, 将 vptr 指向对应的 vtable, 将类与此类的 vtable 联系了起来. 另外在调用类的构造函数时, 指向基础类的指针此时已经变成指向具体的类的 this 指针, 这样依靠此 this 指针即可得到正确的 vtable.
如此才能真正与函数体进行连接, 这就是动态联编, 实现多态的基本原理.
注意: 一定要区分虚函数, 纯虚函数, 虚拟继承的关系和区别. 牢记虚函数实现原理, 因为多态 C++ 面试的重要考点之一, 而虚函数是实现多态的基础.
8. c++ 空类的成员函数
缺省的构造函数
缺省的拷贝构造函数
缺省的赋值运算符
缺省的析构函数
缺省的取址运算符
缺省的取址运算符 const
注意: 只有当实际使用这些函数的时候, 编译器才会去定义它们.
9. 谈谈你对拷贝构造函数和赋值运算符的认识
两个不同之处:
1 拷贝构造函数生成新的类对象, 而赋值运算符不能.
2 由于拷贝构造函数是直接构造一个新的类对象, 所以在初始化这个对象之前不用检验源对象是否和新建对象相同. 而赋值运算符则需要这个操作, 另外赋值运算中如果原来的对象中有内存分配要先把内存释放掉.
注意: 当有类中有指针类型的成员变量时, 一定要重写拷贝构造函数和赋值运算符, 不要使用默认的.
10. 用 C++ 设计一个不能被继承的类
- class Base
- {
- private:
- Base() {
- }
- ~Base() {
- }
- };
- class Derived : public Base
- {
- public:
- Derived() : Base() {
- }
- ~Derived() {
- }
- };
C++ 中的流对象就是采用这样的原理. 防止被赋值, 复制.
11. 类成员的重写, 重载和隐藏的区别
重写和重载主要有以下几点不同:
1 范围的区别: 被重写的和重写的函数在两个类中, 而重载和被重载的函数在同一个类中.
2 参数的区别: 被重写函数和重写函数的参数列表一定相同, 而被重载函数和重载函数的参数列表一定不同.
3 virtual 的区别: 重写的基类中被重写的函数必须要有 virtual 修饰, 而重载函数和被重载函数可以被 virtual 修饰, 也可以没有.
隐藏和重写, 重载有以下几点不同:
1 与重载的范围不同: 和重写一样, 隐藏函数和被隐藏函数不在同一个类中
2 参数的区别: 隐藏函数和被隐藏的函数的参数列表可以相同, 也可不同, 但是函数名肯定要相同. 当参数不相同时, 无论基类中的参数是否被 virtual 修饰, 基类的函数都是被隐藏, 而不是被重写
说明: 虽然重载和覆盖都是实现多态的基础, 但是两者实现的技术完全不相同, 达到的目的也是完全不同的, 覆盖是动态绑定的多态, 而重载是静态绑定的多态.
12. extern 有什么作用
extern 标识的变量或者函数声明其定义在别的文件中, 提示编译器遇到此变量和函数时在其它模块中寻找其定义.
13. 引用和指针区别
1 引用必须被初始化, 但是不分配存储空间. 指针不必在声明时初始化, 在初始化的时候需要分配存储空间
2 引用初始化以后不能被改变, 指针可以改变所指的对象
3 不存在指向空值的引用, 但是存在指向空值的指针
14. 数组指针
- #include <iostream>
- using namespace std;
- int main()
- {
- int a[5] = {
- 1, 2, 3, 4, 5
- };
- int *ptr = (int*)(&a+1);
- cout << *(ptr-1) << "\t" << *(ptr-2) << endl; // 5 4
- cout << "----------------" << endl;
- int *p = (int *)(a+1); //2
- cout << *p << endl;
- }
15. const int *a 和 int * const a 区别
- int main()
- {
- int b = 3;
- int c = 4;
- const int *p = &b; // 等价于 int const *p = &b;
- p = &c; // 修饰值, 指针可变
- //*p = 5;//error 修饰值, 值不可变
- cout << *p << endl;
- int a = 5;
- int * const q = &a; // 修饰指针
- //p = &c;//error 修饰指针, 指针不可变
- *p = 5; // 修饰指针, 值可变
- }
来源: http://www.jianshu.com/p/66fce1081d25