console.log(🐂🍺);|

[C++]基础题目总结

C++期末复习

一、选择题考察的知识点(40分)

 

C++在C的基础上多了什么新特性

  • 类和对象
  • 继承
  • 多态、虚函数和RTT1(运行阶段类型识别)
  • 函数重载
  • 引用变量
  • 泛型(独立于类型的)编程,这种技术是由模版和标准模版库(STL)提供的
  • 处理错误条件的异常机制
  • 管理函数、类和变量名的名称空间

 

编写C++程序的步骤

1、用C++语言编写程序
用高级语言编写的程序称为“源程序”(source program)。C++的源程序是以.cpp作为后缀的(cpp是c plus plus 的缩写)。
2、对源程序进行编译
为了使计算机能执行高级语言源程序,必须先用一种称为“编译器(complier)”的软件(也称编译程序或编译系统),把源程序翻译成二进制形式的“目标程序(object program)”。
编译是以源程序文件为单位分别编译的。目标程序一般以.obj或.o作为后缀(object 的缩写)。编译的作用是对源程序进行词法检查和语法检查。编译时对文件中的全部内容进行检查,编译结束后会显示出所有的编译出错信息。一般编译系统给出的出错信息分为两种,一种是错误(error);一种是警告(warning) 。
3、将目标文件连接
在改正所有的错误并全部通过编译后,得到一个或多个目标文件。此时要用系统提供的“连接程序(linker)”将一个程序的所有目标程序和系统的库文件以及系统提供的其他信息连接起来,最终形成一个可执行的二进制文件,它的后缀是.exe,是可以直接执行的。
4、运行程序
运行最终形成的可执行的二进制文件(.exe文件),得到运行结果。
5、分析运行结果
如果运行结果不正确,应检查程序或算法是否有问题。

 

C++中合法标识符的特征

第一个字符必须是字母(不分大小写)或下划线(_);
后跟字母(不分大小写)、下划线(_)或数字组成;
标识符中的大小写字母有区别;
不能与c编译系统已经预定义的、具有特殊用途的保留标识符(即关键字)同名。比如,不能将标识符命名为float,auto,break,case,this,try,for,while,int,char,short, unsigned,等等;

 

算符优先级(重点考察算术运算符、逻辑运算符和赋值运算符)

 

C++运算符优先级表,从上到下,从左到右,优先级依次减弱。
优先级运算符说明结合性
1 :: 范围解析 自左向右
2 ++  -- 后缀自增/后缀自减
() 括号
[] 数组下标
. 成员选择(对象)
−> 成员选择(指针)
3 ++  -- 前缀自增/前缀自减 自右向左
+  − 加/减
!  ~ 逻辑非/按位取反
(type) 强制类型转换
* 取指针指向的值
& 某某的地址
sizeof 某某的大小
new,new[] 动态内存分配/动态数组内存分配
delete,delete[] 动态内存释放/动态数组内存释放
4 .*  ->* 成员对象选择/成员指针选择 自左向右
5 *  /   % 乘法/除法/取余
6 +  − 加号/减号
7 <<  >> 位左移/位右移
8 <  <= 小于/小于等于
>  >= 大于/大于等于
9 ==  != 等于/不等于
10 & 按位与
11 ^ 按位异或
12 | 按位或
13 && 与运算
14 || 或运算
15 ?: 三目运算符 自右向左
16 = 赋值
+=  −= 相加后赋值/相减后赋值
*=  /=   %= 相乘后赋值/相除后赋值/取余后赋值
<<=  >>= 位左移赋值/位右移赋值
&=  ^=  |= 位与运算后赋值/位异或运算后赋值/位或运算后赋值
17 throw 抛出异常
18 , 逗号 自左向右

 

算术表达式、逻辑表达式

算术运算符

下表显示了 C++ 支持的算术运算符。

假设变量 A 的值为 10,变量 B 的值为 20,则:

运算符描述实例
+ 把两个操作数相加 A + B 将得到 30
- 从第一个操作数中减去第二个操作数 A - B 将得到 -10
* 把两个操作数相乘 A * B 将得到 200
/ 分子除以分母 B / A 将得到 2
% 取模运算符,整除后的余数 B % A 将得到 0
++ 自增运算符,整数值增加 1 A++ 将得到 11
-- 自减运算符,整数值减少 1 A-- 将得到 9

逻辑运算符

下表显示了 C++ 支持的关系逻辑运算符。

假设变量 A 的值为 1,变量 B 的值为 0,则:

运算符描述实例
&& 称为逻辑与运算符。如果两个操作数都非零,则条件为真。 (A && B) 为假。
|| 称为逻辑或运算符。如果两个操作数中有任意一个非零,则条件为真。 (A || B) 为真。
! 称为逻辑非运算符。用来逆转操作数的逻辑状态。如果条件为真则逻辑非运算符将使其为假。 !(A && B) 为真。

 

条件判断语句if的使用方法

 

 

 

循环语句(while, for)的使用方法

while循环语法

C++ 中 while 循环的语法:

while(condition)
{
   statement(s);
}

在这里,statement(s) 可以是一个单独的语句,也可以是几个语句组成的代码块。condition 可以是任意的表达式,当为任意非零值时都为真。当条件为真时执行循环。

当条件为假时,程序流将继续执行紧接着循环的下一条语句。

流程图

C++ 中的 while 循环

在这里,while 循环的关键点是循环可能一次都不会执行。当条件被测试且结果为假时,会跳过循环主体,直接执行紧接着 while 循环的下一条语句。

 

for循环语法

C++ 中 for 循环的语法:

for ( init; condition; increment )
{
   statement(s);
}

下面是 for 循环的控制流:

  1. init 会首先被执行,且只会执行一次。这一步允许您声明并初始化任何循环控制变量。您也可以不在这里写任何语句,只要有一个分号出现即可。
  2. 接下来,会判断 condition。如果为真,则执行循环主体。如果为假,则不执行循环主体,且控制流会跳转到紧接着 for 循环的下一条语句。
  3. 在执行完 for 循环主体后,控制流会跳回上面的 increment 语句。该语句允许您更新循环控制变量。该语句可以留空,只要在条件后有一个分号出现即可。
  4. 条件再次被判断。如果为真,则执行循环,这个过程会不断重复(循环主体,然后增加步值,再然后重新判断条件)。在条件变为假时,for 循环终止。

流程图

 

函数默认参数的使用方法(注意默认参数和非默认参数的顺序)

① 有函数声明(原型)时,默认参数可以放在函数声明或者定义中,但只能放在二者之一

double sqrt(double f = 1.0); //函数声明

double sqrt(double f)  //函数定义
{
  // ....  
} 

 

② 没有函数(原型)时,默认参数在函数定义时指定.

//没有 函数声明

double sqrt(double f = 1.0)  //函数定义

 

③ 在具有多个参数的函数中指定默认值时,默认参数都必须出现在不默认参数的右边,一旦某个参数开始指定默认值,它右边的所有参数都必须指定默认值.

int f (int i1, int i2 = 2, int i3 = 3);     // 正确
int g (int i1, int i2 = 2, int i3);         // 错误, i3未指定默认值
int h (int i1 = 1, int i2, int i3 = 3);     // 错误, i2未指定默认值

 

④ 在调用具有默认参数的函数时, 若某个实参默认,其右边的所有实参都应该默认。

复制代码
//例如, 一个函数声明如下
int f(int i1 = 1, int i2 =2, int i3 = 3);


//调用函数 f()
f();             //正确, i1=1, i2=2, i3=3
f(3);            //正确, i1=3, i2=2, i3=3
f(2, 3);         //正确, i1=2, i2=3, i3=3
f(4, 5, 6);      //正确, i1=4, i2=5, i3=6
f(, 2, 3);       //错误, i1默认,其右边的i2和i3没有默认
复制代码

 

重载函数的定义

一.重载函数的定义

函数的重载是一种特殊情况,C++允许在同一作用域中声明几个类似的同名函数,这些同名函数的形参列表

(参数个数,类型,顺序)必须不同,常用来处理实现功能类似数据类型不同的问题。

在C++中不仅函数可以重载,运算符也可以重载。

运算符<<>>。既可以做移位运算符,也可以做输出,输入运算符。
注意:重载函数的参数个数,参数类型或参数顺序三者中必须有一个不同。

 

函数重载的规则:

*函数名称必须相同。

*参数列表必须不同(个数不同,类型不同,参数排列顺序不同等)。

*函数的返回类型也可以相同也可以不相同。

*仅仅返回类型不同不足以成为函数的重载。

 

二.函数重载的作用

重载函数通常用来在同一作用域内 用同一个函数名 命名一组功能相似的函数,

这样做减少了函数名的数量,避免了名字空间的污染,也大大方便了代码的书写,可读性很强。

 

三.重载底层编译的原理

因为函数名一样,我们根据参数列表对函数进行重命名,

这样的话底层编译就可以识别不同功能的重载函数了。

例:

复制代码
void Swap(int a , int b);

         Swap_int_int;

        void Swap(float a, float b);

       Swap_float_float;

       void Swap(float a, float b);

       Swap_float_float;
复制代码

 

我们可以这样去理解,先重命名函数,再去调用函数。

 

数组的定义,数组下标的范围,数组元素的使用方法(下标引用,指针引用)

菜鸟教程(数组) https://www.runoob.com/cplusplus/cpp-arrays.html

 

初始化字符数组的方法(注意字符串以’\0’结尾,多占一个字符)

发现了一个字符数组初始化的误区,而这个往往能导致比较严重的性能问题,分析介绍如下:
往往我们在初始化一个字符 数组,大概有如下几种写法:

char array1[1024] = "";
char array2[1024] = {0};
char array3[1024] = {'\0'};
char array4[1024];
array4[0] = '\0';

 

但这四种写法,其实代表含义不同,看起来前三种写法只是将array的第一个字符置为0,其实前三种在gcc编译时,都是调用了memset来将整个array置为0,如果这个array很长,其实也会导致性能问题。我写了一个简单的小程序编译生成test,objdump了一 下,执行“objdump -S test”可以看下面的代码:

复制代码
 1 int main() {
 2 400698: 55 push %rbp
 3 400699: 48 89 e5 mov %rsp,%rbp
 4 40069c: 48 81 ec 00 10 00 00 sub $0x1000,%rsp
 5 char array1[1024] = "";
 6 4006a3: 0f b6 05 42 01 00 00 movzbl 322(%rip),%eax # 4007ec <_IO_stdin_used+0x4>
 7 4006aa: 88 85 00 fc ff ff mov %al,0xfffffffffffffc00(%rbp)
 8 4006b0: 48 8d bd 01 fc ff ff lea 0xfffffffffffffc01(%rbp),%rdi
 9 4006b7: ba ff 03 00 00 mov $0x3ff,%edx
10 4006bc: be 00 00 00 00 mov $0x0,%esi
11 4006c1: e8 fa fe ff ff callq 4005c0 <memset@plt> //调用了memset
12 
13 char array2[1024] = {0};
14 4006c6: 48 8d bd 00 f8 ff ff lea 0xfffffffffffff800(%rbp),%rdi
15 4006cd: ba 00 04 00 00 mov $0x400,%edx
16 4006d2: be 00 00 00 00 mov $0x0,%esi
17 4006d7: e8 e4 fe ff ff callq 4005c0 <memset@plt> //调用了memset
18 
19 char array3[1024] = {'\0'};
20 4006dc: 48 8d bd 00 f4 ff ff lea 0xfffffffffffff400(%rbp),%rdi
21 4006e3: ba 00 04 00 00 mov $0x400,%edx
22 4006e8: be 00 00 00 00 mov $0x0,%esi
23 4006ed: e8 ce fe ff ff callq 4005c0 <memset@plt> //调用了memset
24 
25 char array4[1024];
26 array4[0] = '\0';
27 4006f2: c6 85 00 f0 ff ff 00 movb $0x0,0xfffffffffffff000(%rbp)
28 
29 return 0;
30 4006f9: b8 00 00 00 00 mov $0x0,%eax
31 }
复制代码

所以,对这四种写法,实际执行的代码解释如下:

char array1[1024] = ""; //第11行,调用memset将1023个字符置为0
char array2[1024] = {0}; //第17行,调用memset将1024个字符置为0
char array3[1024] = {'\0'}; //第23行,调用memset将1024个字符置为0
char array4[1024];
array4[0] = '\0'; //只是将第一个字符置为0

而对于字符数组,往往只是作为一个字符串的临时缓冲区使用,没有必要将整个数组置为0,所以第四种写法往往就能达到初始化的目的。建议使用第四种写法来初始化一个字符数组,这样能节约很多性能消耗。

转载需注明来源:http://www.cnblogs.com/yczcc/p/7595099.html

 

引用的建立方法

1. 引用基本用法

引用是c++对c的重要扩充。在c/c++中指针的作用基本都是一样的,但是c++增加了另外一种给函数传递地址的途径,这就是按引用传递(pass-by-reference),它也存在于其他一些编程语言中,并不是c++的发明。

 

变量名实质上是一段连续内存空间的别名,是一个标号(门牌号)

程序中通过变量来申请并命名内存空间

通过变量的名字可以使用存储空间

 

对一段连续的内存空间只能取一个别名吗?

c++中新增了引用的概念,引用可以作为一个已定义变量的别名。

基本语法:

Type& ref = val;

注意事项:

&在此不是求地址运算,而是起标识作用。

类型标识符是指目标变量的类型

必须在声明引用变量时进行初始化。

引用初始化之后不能改变。

不能有NULL引用。必须确保引用是和一块合法的存储单元关联。

可以建立对数组的引用。

复制代码
//1. 认识引用
void test01(){

    int a = 10;
    //给变量a取一个别名b
    int& b = a;
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
    cout << "------------" << endl;
    //操作b就相当于操作a本身
    b = 100;
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
    cout << "------------" << endl;
    //一个变量可以有n个别名
    int& c = a;
    c = 200;
    cout << "a:" << a << endl;
    cout << "b:" << b << endl;
    cout << "c:" << c << endl;
    cout << "------------" << endl;
    //a,b,c的地址都是相同的
    cout << "a:" << &a << endl;
    cout << "b:" << &b << endl;
    cout << "c:" << &c << endl;
}
//2. 使用引用注意事项
void test02(){
    //1) 引用必须初始化
    //int& ref; //报错:必须初始化引用
    //2) 引用一旦初始化,不能改变引用
    int a = 10;
    int b = 20;
    int& ref = a;
    ref = b; //不能改变引用
    //3) 不能对数组建立引用
    int arr[10];
    //int& ref3[10] = arr;
}

    //1. 建立数组引用方法一
    typedef int ArrRef[10];
    int arr[10];
    ArrRef& aRef = arr;
    for (int i = 0; i < 10;i ++){
        aRef[i] = i+1;
    }
    for (int i = 0; i < 10;i++){
        cout << arr[i] << " ";
    }
    cout << endl;
    //2. 建立数组引用方法二
    int(&f)[10] = arr;
    for (int i = 0; i < 10; i++){
        f[i] = i+10;
    }
    for (int i = 0; i < 10; i++){
        cout << arr[i] << " ";
    }
    cout << endl;
复制代码

 

 

 

2. 函数中的引用

 

最常见看见引用的地方是在函数参数和返回值中。当引用被用作函数参数的时,在函数内对任何引用的修改,将对还函数外的参数产生改变。当然,可以通过传递一个指针来做相同的事情,但引用具有更清晰的语法。

如果从函数中返回一个引用,必须像从函数中返回一个指针一样对待。当函数返回值时,引用关联的内存一定要存在。

复制代码
//值传递
void ValueSwap(int m,int n){
    int temp = m;
    m = n;
    n = temp;
}
//地址传递
void PointerSwap(int* m,int* n){
    int temp = *m;
    *m = *n;
    *n = temp;
}
//引用传递
void ReferenceSwap(int& m,int& n){
    int temp = m;
    m = n;
    n = temp;
}
void test(){
    int a = 10;
    int b = 20;
    //值传递
    ValueSwap(a, b);
    cout << "a:" << a << " b:" << b << endl;
    //地址传递
    PointerSwap(&a, &b);
    cout << "a:" << a << " b:" << b << endl;
    //引用传递
    ReferenceSwap(a, b);
    cout << "a:" << a << " b:" << b << endl;
}
复制代码

 

 

通过引用参数产生的效果同按地址传递是一样的。引用的语法更清楚简单:

1) 函数调用时传递的实参不必加“&”符

2) 在被调函数中不必在参数前加“*”符

引用作为其它变量的别名而存在,因此在一些场合可以代替指针。C++主张用引用传递取代地址传递的方式,因为引用语法容易且不易出错。

 

不能返回局部变量的引用。

函数当左值,必须返回引用。

复制代码
//返回局部变量引用
int& TestFun01(){
    int a = 10; //局部变量
    return a;
}
//返回静态变量引用
int& TestFunc02(){    
    static int a = 20;
    cout << "static int a : " << a << endl;
    return a;
}
int main(){
    //不能返回局部变量的引用
    int& ret01 = TestFun01();
    //如果函数做左值,那么必须返回引用
    TestFunc02();
    TestFunc02() = 100;
    TestFunc02();

    return EXIT_SUCCESS;
}
复制代码

 

 

3. 引用的本质

 

引用的本质在c++内部实现是一个指针常量.

Type& ref = val; // Type* const ref = &val;

 

c++编译器在编译过程中使用常指针作为引用的内部实现,因此引用所占用的空间大小与指针相同,只是这个过程是编译器内部实现,用户不可见。

复制代码
//发现是引用,转换为 int* const ref = &a;
void testFunc(int& ref){
    ref = 100; // ref是引用,转换为*ref = 100
}
int main(){
    int a = 10;
    int& aRef = a; //自动转换为 int* const aRef = &a;这也能说明引用为什么必须初始化
    aRef = 20; //内部发现aRef是引用,自动帮我们转换为: *aRef = 20;
    cout << "a:" << a << endl;
    cout << "aRef:" << aRef << endl;
    testFunc(a);
    return EXIT_SUCCESS;
}
复制代码

 

 

4. 指针引用

 

在c语言中如果想改变一个指针的指向而不是它所指向的内容,函数声明可能这样:

void fun(int**);

给指针变量取一个别名。

Type* pointer = NULL;  

Type*& = pointer;

Type* pointer = NULL;  Type*& = pointer;

复制代码
struct Teacher{
    int mAge;
};
//指针间接修改teacher的年龄
void AllocateAndInitByPointer(Teacher** teacher){
    *teacher = (Teacher*)malloc(sizeof(Teacher));
    (*teacher)->mAge = 200;  
}
//引用修改teacher年龄
void AllocateAndInitByReference(Teacher*& teacher){
    teacher->mAge = 300;
}
void test(){
    //创建Teacher
    Teacher* teacher = NULL;
    //指针间接赋值
    AllocateAndInitByPointer(&teacher);
    cout << "AllocateAndInitByPointer:" << teacher->mAge << endl;
    //引用赋值,将teacher本身传到ChangeAgeByReference函数中
    AllocateAndInitByReference(teacher);
    cout << "AllocateAndInitByReference:" << teacher->mAge << endl;
    free(teacher);
}
复制代码

 

 

对于c++中的定义那个,语法清晰多了。函数参数变成指针的引用,用不着取得指针的地址。

5. 常量引用

 

常量引用的定义格式:

const Type& ref = val;

 

常量引用注意:

1.字面量不能赋给引用,但是可以赋给const引用

2.const修饰的引用,不能修改。

 

复制代码
void test01(){
    int a = 100;
    const int& aRef = a; //此时aRef就是a
    //aRef = 200; 不能通过aRef的值
    a = 100; //OK
    cout << "a:" << a << endl;
    cout << "aRef:" << aRef << endl;
}
void test02(){
    //不能把一个字面量赋给引用
    //int& ref = 100;
    //但是可以把一个字面量赋给常引用
    const int& ref = 100; //int temp = 200; const int& ret = temp;
}
复制代码

 

 

 [const引用使用场景]

    常量引用主要用在函数的形参,尤其是类的拷贝/复制构造函数。

将函数的形参定义为常量引用的好处:

  • 引用不产生新的变量,减少形参与实参传递时的开销。
  • 由于引用可能导致实参随形参改变而改变,将其定义为常量引用可以消除这种副作用。

    如果希望实参随着形参的改变而改变,那么使用一般的引用,如果不希望实参随着形参改变,那么使用常引用。

 

//const int& param防止函数中意外修改数据
void ShowVal(const int& param){
    cout << "param:" << param << endl;
}

 

 

面向对象中成员访问标签的使用方法:公有,私有,保护,注意三者的区别

C++中 public,protected, private 访问标号小结

第一:private, public, protected 访问标号的访问范围。
private:只能由1.该类中的函数、2.其友元函数访问。
不能被任何其他访问,该类的对象也不能访问。

protected:可以被1.该类中的函数、2.子类的函数、以及3.其友元函数访问。
但不能被该类的对象访问。

public:可以被1.该类中的函数、2.子类的函数、3.其友元函数访问,也可以由4.该类的对象访问。
 
注:友元函数包括3种:设为友元的普通的非成员函数;设为友元的其他类的成员函数;设为友元类中的所有成员函数。

第二:类的继承后方法属性变化。
private 属性不能够被继承。
使用private继承父类的protected和public属性在子类中变为private;
使用protected继承,父类的protected和public属性在子类中变为protected;
使用public继承,父类中的protected和public属性不发生改变;

 

静态数据成员初始化方法(注意,如果定义时没赋初值,对于静态成员会初始化为0)

静态成员的初始化:

与全局对象一样对于静态数据成员在程序中也只能提供一个定义,这意味着静态数据成员的初始化不应该被放在头文件中而应该放在含有类的非inline函数定义的文件中。

能在类中初始化的成员只有一种,那就是静态常量成员。

class A
{  
private:
    static const int count = 0; // 静态常量成员可以在类内初始化
};

 

结论:

  1. 静态常量数据成员可以在类内初始化(即类内声明的同时初始化),也可以在类外,即类的实现文件中初始化,不能在构造函数中初始化,也不能在构造函数的初始化列表中初始化;
  2. 静态非常量数据成员只能在类外,即类的实现文件中初始化,也不能在构造函数中初始化,不能在构造函数的初始化列表中初始化;
  3. 非静态的常量数据成员不能在类内初始化,也不能在构造函数中初始化,而只能且必须在构造函数的初始化列表中初始化;
  4. 非静态的非常量数据成员不能在类内初始化,可以在构造函数中初始化,也可以在构造函数的初始化列表中初始化;

 

构造函数、拷贝构造函数、析构函数的使用方法

菜鸟教程(构造&析构) https://www.runoob.com/cplusplus/cpp-constructor-destructor.html

 

虚函数的定义及使用方法(搞清楚虚函数的使用场景)

在某基类中声明为 virtual 并在一个或多个派生类中被重新定 义的成员函数,用法格式为:virtual 函数返回类型 函数名(参数表) {函数体};实现多态性,通过指向派生类的基类指针或引用,访问派生类中同名覆盖成员函数。


  虚函数定义:简单地说,那些被virtual关键字修饰的成员函数,就是虚函数。虚函数的作用,用专业术语来解释就是实现多态性(Polymorphism),多态性是将接口与实现进行分离;用形象的语言来解释就是实现以共同的方法,但因个体差异,而采用不同的策略。


  虚函数的作用:用同一个调用形式,既能调用派生类又能调用基类的同名函数。


  虚函数的使用方法是:


1. 在基类用virtual声明成员函数为虚函数。
这样就可以在派生类中重新定义此函数,为它赋予新的功能,并能方便地被调用。在类外定义虚函数时,不必再加virtual。
2. 在派生类中重新定义此函数,要求函数名、函数类型、函数参数个数和类型全部与基类的虚函数相同,并根据派生类的需要重新定义函数体。
C++规定,当一个成员函数被声明为虚函数后,其派生类中的同名函数都自动成为虚函数。因此在派生类重新声明该虚函数时,可以加virtual,也可以不加,但习惯上一般在每一层声明该函数时都加virtual,使程序更加清晰。如果在派生类中没有对基类的虚函数重新定义,则派生类简单地继承其直接基类的虚函数。
3. 定义一个指向基类对象的指针变量,并使它指向同一类族中需要调用该函数的对象。
4. 通过该指针变量调用此虚函数,此时调用的就是指针变量指向的对象的同名函数。
通过虚函数与指向基类对象的指针变量的配合使用,就能方便地调用同一类族中不同类的同名函数,只要先用基类指针指向即可。如果指针不断地指向同一类族中不同类的对象,就能不断地调用这些对象中的同名函数。这就如同前面说的,不断地告诉出租车司机要去的目的地,然后司机把你送到你要去的地方。

 

虚基类的使用方法,为什么要使用虚基类

在类的继承中,如果我们遇到这种情况:
“B和C同时继承A,而B和C都被D继承”
在此时,假如A中有一个函数fun()当然同时被B和C继承,而D按理说继承了B和C,同时也应该能调用fun()函数。这一调用就有问题了,到底是要调用B中的fun()函数还是调用C中的fun()函数呢?在C++中,有两种方法实现调用:
(注意:这两种方法效果是不同的)

  1.     使用作用域标识符来唯一表示它们比如:B::fun()
  2.     另一种方法是定义虚基类,使派生类中只保留一份拷贝。

 

继承有哪几种方式,每种方式的特点是什么?

 

 

 

 

 

如果在定义派生类时在基类前没有指定访问标签,默认情况下是哪种继承的方式?

private 私有继承

 

输入输出流对象的使用方法

 

new, delete语句的使用方法

对于计算机程序设计而言,变量和对象在内存中的分配都是编译器在编译程序时安排好的,这带来了极大的不便,如数组必须大开小用,指针必须指向一个已经存在的变量或对象。对于不能确定需要占用多少内存的情况,动态内存分配解决了这个问题。

    new和delete运算符是用于动态分配和撤销内存的运算符。

一、new用法

1.开辟单变量地址空间

   使用new运算符时必须已知数据类型,new运算符会向系统堆区申请足够的存储空间,如果申请成功,就返回该内存块的首地址,如果申请不成功,则返回零值。

    new运算符返回的是一个指向所分配类型变量(对象)的指针。对所创建的变量或对象,都是通过该指针来间接操作的,而动态创建的对象本身没有标识符名。

 一般使用格式:
        格式1:指针变量名=new 类型标识符;
        格式2:指针变量名=new 类型标识符(初始值);
        格式3:指针变量名=new 类型标识符 [内存单元个数];

说明:格式1和格式2都是申请分配某一数据类型所占字节数的内存空间;但是格式2在内存分配成功后,同时将一初值存放到该内存单元中;而格式3可同时分配若干个内存单元,相当于形成一个动态数组。例如:

    1)new int;  //开辟一个存放整数的存储空间,返回一个指向该存储空间的地址。int *a = new int 即为将一个int类型的地址赋值给整型指针a

    2)int *a = new int(5) 作用同上,但是同时将整数空间赋值为5

2.开辟数组空间

    对于数组进行动态分配的格式为:

       指针变量名=new 类型名[下标表达式];
       delete [ ] 指向该数组的指针变量名;

    两式中的方括号是非常重要的,两者必须配对使用,如果delete语句中少了方括号,因编译器认为该指针是指向数组第一个元素的指针,会产生回收不彻底的问题(只回收了第一个元素所占空间),加了方括号后就转化为指向数组的指针,回收整个数组。

    delete []的方括号中不需要填数组元素数,系统自知。即使写了,编译器也忽略。

    请注意“下标表达式”不必是常量表达式,即它的值不必在编译时确定,可以在运行时确定。

    一维: int *a = new int[100];    //开辟一个大小为100的整型数组空间

    二维: int **a = new int[5][6]

    三维及其以上:依此类推.

    一般用法: new 类型 (初值)

二、delete用法

1. 删除单变量地址空间

       int *a = new int;

       delete a;   //释放单个int的空间

2. 删除数组空间

       int *a = new int[5];

       delete []a;    //释放int数组空间

三、使用注意事项

1. new 和delete都是内建的操作符,语言本身所固定了,无法重新定制,想要定制new和delete的行为,徒劳无功的行为。

2. 动态分配失败,则返回一个空指针(NULL),表示发生了异常,堆资源不足,分配失败。

3. 指针删除与堆空间释放。删除一个指针p(delete p;)实际意思是删除了p所指的目标(变量或对象等),释放了它所占的堆空间,而不是删除p本身(指针p本身并没有撤销,它自己仍然存在,该指针所占内存空间并未释放),释放堆空间后,p成了空指针。

4. 内存泄漏(memory leak)和重复释放。new与delete 是配对使用的, delete只能释放堆空间。如果new返回的指针值丢失,则所分配的堆空间无法回收,称内存泄漏,同一空间重复释放也是危险的,因为该空间可能已另分配,所以必须妥善保存new返回的指针,以保证不发生内存泄漏,也必须保证不会重复释放堆内存空间。

5. 动态分配的变量或对象的生命期。我们也称堆空间为自由空间(free store),但必须记住释放该对象所占堆空间,并只能释放一次,在函数内建立,而在函数外释放,往往会出错。

6. 要访问new所开辟的结构体空间,无法直接通过变量名进行,只能通过赋值的指针进行访问。

    用new和delete可以动态开辟和撤销地址空间。在编程序时,若用完一个变量(一般是暂时存储的数据),下次需要再用,但却又想省去重新初始化的功夫,可以在每次开始使用时开辟一个空间,在用完后撤销它。

什么函数会有this指针?友元函数是成员函数吗?友元的作用是什么?

 

 

 

 

二、编程题(60分)

基本循环(1题)

编写程序,计算下式的值 1-3+5-7….-99

复制代码
// 编写程序,计算下式的值 1-3+5-7….-99
#include<iostream>
using namespace std;

int main(){
    int num,flag;
    num=flag=1;
    int sum=0;
    while(num<100){
        sum+=num*flag;
        num+=2;
        flag=-flag;
    }
    cout<<sum<<endl;
    system("pause");
}
复制代码

编写程序,计算并输出半径r = 1到r = 20之间半径为整数的圆形的面积,直到面积大于100为止

复制代码
// 编写程序,计算并输出半径r = 1到r = 20之间半径为整数的圆形的面积,直到面积大于100为止
#include<iostream>
#include<cmath>
using namespace std;

//利用反三角函数定义宏Pi
#define Pi acos(-1)

int main(){
    for(int r=1;r++;r<20){
        int s=Pi*pow(r,2);
        if(s>100){
            break;
        }
        cout<<s<<endl;
    }
    system("pause");
}
复制代码

 

水仙花数怎么计算,完数怎么计算等,具体看一下循环章节的练习题

复制代码
// 水仙花数
#include<iostream>
using namespace std;

//定义宏
#define f(a) (a)*(a)*(a)

int main(){
    for(int i=100;i<1000;i++){
        //判断条件
        if(f(i%10)+f(i/10%10)+f(i/100%10)==i){
            cout<<i<<endl;
        }
    }
    system("pause");
    return 0;
}
复制代码
复制代码
// 完数
#include<iostream>
using namespace std;

int main()
{
    int i,j,k,sum;
    cout<<" 1000以内的完数有:"<<endl;
    for(i=2;i<=1000;i++)
    {
        k=i/2;
        sum=0;
        for(j=1;j<=k;j++)

        if(i%j==0)
            sum=sum+j;

        if(sum==i)
            cout<<"sum="<<sum<<endl;
    }
    return 0;
    system("pause");
}
复制代码

 

 

数组使用方法(1题)

 

有如下矩阵,编写三个函数,分别计算每行的和、每列的和,矩阵中最大元素所在的位置

复制代码
//矩阵 求各行各列的和
#include<iostream>
using namespace std;

int main(){
    // 矩阵的定义
    int arr[4][3]={{12,2,3},{4,5,6},{7,8,9},{10,11,12}};

    // 二维数组行求和
    for(int i=0;i<4;i++){
        int rowSum = 0;
        for(int j=0;j<3;j++){
            rowSum += arr[i][j];
        }
        cout<<""<<i<<"行的和"<<rowSum<<endl;
    }

    //  二维数组列求和
    for(int j=0;j<3;j++){
        int columnSum = 0;
        for(int i=0;i<4;i++){
            columnSum += arr[j][i];
        }
        cout<<""<<j<<"列的和"<<columnSum<<endl;
    }

    // 矩阵最大元素 - 冒泡排序
    int max = 0;
    for(int i=0;i<4;i++){
        for(int j=0;j<3;j++){
            if(arr[i][j]>max){
                max = arr[i][j];
            }
        }
    }
    cout<<"矩阵中的最大元素是:"<<max<<endl;
    
    system("pause");
}
复制代码

 

编写函数,求矩阵Y的值

题目有问题,考察矩阵乘法操作

 

字符串操作(1题)

给定一个字符串,写函数判断字符串中每个数字字符出现的频率

复制代码
#include<iostream>
#include<string>

using namespace std;
int main()
{
    string str;
    cout<<"input some text:"<<endl;
    //输入一个字符串,传给字符串变量str
    getline(cin,str);
    int frequency[256]={};
    for(int i=0;i<str.size();i++){
        frequency[(int)str[i]]++;
        // cnt[str[i]]++与str[i]相对应的字符个数增加一个
    }
    for(int i=0;i<256;i++)//输出字符出现次数
    {
        if(frequency[i]!=0)
        // cnt[i]!=0遇到串结符也就是\0就等于0
            cout<<(char)i<<':'<<frequency[i]<<""<<endl;
    }
}
复制代码

 

给定一个字符串,写函数判断字符串中是否包含另一个字符串

给定一个字符串,写函数判断该字符串是否为回文字串(正读反读均相同)

 

面向对象编程(1题) ---- 多态章节

多态的基本概念

多态是C++面向对象三大特性之一

多态分为两类

  • 静态多态: 函数重载 和 运算符重载属于静态多态,复用函数名
  • 动态多态: 派生类和虚函数实现运行时多态

静态多态和动态多态区别:

  • 静态多态的函数地址早绑定 - 编译阶段确定函数地址
  • 动态多态的函数地址晚绑定 - 运行阶段确定函数地址

下面通过案例进行讲解多态

复制代码
class Animal
{
public:
    //Speak函数就是虚函数
    //函数前面加上virtual关键字,变成虚函数,那么编译器在编译的时候就不能确定函数调用了。
    virtual void speak()
    {
        cout << "动物在说话" << endl;
    }
};

class Cat :public Animal
{
public:
    void speak()
    {
        cout << "小猫在说话" << endl;
    }
};

class Dog :public Animal
{
public:

    void speak()
    {
        cout << "小狗在说话" << endl;
    }

};
//我们希望传入什么对象,那么就调用什么对象的函数
//如果函数地址在编译阶段就能确定,那么静态联编
//如果函数地址在运行阶段才能确定,就是动态联编

void DoSpeak(Animal & animal)
{
    animal.speak();
}
//
//多态满足条件: 
//1、有继承关系
//2、子类重写父类中的虚函数
//多态使用:
//父类指针或引用指向子类对象

void test01()
{
    Cat cat;
    DoSpeak(cat);


    Dog dog;
    DoSpeak(dog);
}


int main() {

    test01();

    system("pause");

    return 0;
}
复制代码

 

总结:

多态满足条件

  • 有继承关系
  • 子类重写父类中的虚函数

多态使用条件

  • 父类指针或引用指向子类对象

重写:函数返回值类型 函数名 参数列表 完全一致称为重写

4.7.2 多态案例一-计算器类

案例描述:

分别利用普通写法和多态技术,设计实现两个操作数进行运算的计算器类

多态的优点:

  • 代码组织结构清晰
  • 可读性强
  • 利于前期和后期的扩展以及维护

示例:

复制代码
//普通实现
class Calculator {
public:
    int getResult(string oper)
    {
        if (oper == "+") {
            return m_Num1 + m_Num2;
        }
        else if (oper == "-") {
            return m_Num1 - m_Num2;
        }
        else if (oper == "*") {
            return m_Num1 * m_Num2;
        }
        //如果要提供新的运算,需要修改源码
    }
public:
    int m_Num1;
    int m_Num2;
};

void test01()
{
    //普通实现测试
    Calculator c;
    c.m_Num1 = 10;
    c.m_Num2 = 10;
    cout << c.m_Num1 << " + " << c.m_Num2 << " = " << c.getResult("+") << endl;

    cout << c.m_Num1 << " - " << c.m_Num2 << " = " << c.getResult("-") << endl;

    cout << c.m_Num1 << " * " << c.m_Num2 << " = " << c.getResult("*") << endl;
}
复制代码

 

复制代码
//多态实现
//抽象计算器类
//多态优点:代码组织结构清晰,可读性强,利于前期和后期的扩展以及维护
class AbstractCalculator
{
public :

    virtual int getResult()
    {
        return 0;
    }

    int m_Num1;
    int m_Num2;
};

//加法计算器
class AddCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 + m_Num2;
    }
};

//减法计算器
class SubCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 - m_Num2;
    }
};

//乘法计算器
class MulCalculator :public AbstractCalculator
{
public:
    int getResult()
    {
        return m_Num1 * m_Num2;
    }
};


void test02()
{
    //创建加法计算器
    AbstractCalculator *abc = new AddCalculator;
    abc->m_Num1 = 10;
    abc->m_Num2 = 10;
    cout << abc->m_Num1 << " + " << abc->m_Num2 << " = " << abc->getResult() << endl;
    delete abc;  //用完了记得销毁

    //创建减法计算器
    abc = new SubCalculator;
    abc->m_Num1 = 10;
    abc->m_Num2 = 10;
    cout << abc->m_Num1 << " - " << abc->m_Num2 << " = " << abc->getResult() << endl;
    delete abc;  

    //创建乘法计算器
    abc = new MulCalculator;
    abc->m_Num1 = 10;
    abc->m_Num2 = 10;
    cout << abc->m_Num1 << " * " << abc->m_Num2 << " = " << abc->getResult() << endl;
    delete abc;
}

int main() {

    //test01();

    test02();

    system("pause");

    return 0;
}
复制代码

 



总结:C++开发提倡利用多态设计程序架构,因为多态优点很多

 

本文作者:SkyBiuBiu

本文链接:https://www.cnblogs.com/Skybiubiu/p/13305864.html

版权声明:本作品采用知识共享署名-非商业性使用-禁止演绎 2.5 中国大陆许可协议进行许可。

posted @   SkyBiuBiu  阅读(1909)  评论(0编辑  收藏  举报
点击右上角即可分享
微信分享提示
评论
收藏
关注
推荐
深色
回顶
收起