C++98关键字:63个

一、命名空间规则(namespace

1.命名空间可以解决函数、全局变量名重复的问题,包在不同的命名空间里的重复函数,实际就是两个完全无关的函数。

2.命名空间允许续嵌套:嵌套内部和外部没有任何关系

3.命名空间允许重名重名的命名空间就会被合并

注意:一个命名空间就定义了一个新的作用域,命名空间中的所有内容都局限于该命名空间中。它解决了C语言中只能用文件划分作用域的缺点。

命名空间使用有3种方式。

第1种:加命名空间名称及作用于限定符

             int main() { printf("%d\n", N::a); return 0; }

第2种:(常用)

            使用using关键字可以引用一个命名空间,使得这个命名空间中的所有内容对自己可见。

            using N::b;

第3种:使用using namespace命名空间名称引入

            using namespace N;

如果两个命名空间中分别有一模一样的两个函数(参数列表,函数名一样),那么即使同时用using引用了两个命名空间也不会报错。除非使用了两个函数,因为使用函数会产生歧义,也不知道该调哪个。

二、C++输入/输出(cout,cin)必须包含<iostream>,using namespace std;

c语言和C++关于函数方面的区别:

                        函数返回值类型,函数参数类型,C+函数参数可以带默认值,函数命名规则不同。
                        C++语言编译器比C对函数参数、返回值类型更加严格。
                        C语言没有显式写返回值类型时,C语言默认返回值类型为int。

三、 缺省参数缺省参数是声明或定义函数时为函数的参数指定一个默认值

在调用该函数时,如果没有指定实参则采用该默认值,否则使用指定的实参。

      缺省参数不能在函数声明和定义中同时出现

      缺省值必须是常量或全局变量。

1.如果传入了,那么会取传入的值,如果没有传入,会取函数定义时被赋予的值(默认值)

2.这些参数必须位于参数列表的尾部,不能穿插

3.被赋值的参数列表不能在声明和定义中同时出现,只能出现一次

4.函数的参数列表中,允许存在被赋值的参数,

分类:1全缺省参数、2半缺省参数(从右往左依次给出,不能间隔给)

四、函数重载(在相同作用域中,函数名字必须相同,参数列表不同。)

C++允许两个重名函数同时存在,但他们的参数列表不能完全相同,(参数个数/类型/顺序)。

运行程序:预处理、编译、汇编、链接。

函数重载调用原理:

        编译器在编译阶段,会对函数实参类型进行推演,根据结果找类型匹配的函数进行调用。如果有类型完全匹配的函数,则直接进行调用。如果没有类型完全匹配的函数,则会进行隐式类型转换,若隐式类型转换后,有对应的函数则调用,若没有就报错。

C语言不支持函数重载,C++到底是怎么支持函数重载?
        1. C++和C语言编译器对函数名字修改规则不一样。
        2.VS是一个集成开发环境,当点击生成或重新生成时:预处理-编译.obj-汇编-链接-生成可执行代码。
        3.C/C++编译器对源文件是单独进行编译的,函数只有声明,没有定义可以编译通过,但不能链接。C语言只是加下划线_Add
        4.C++编译器在编译函数时,会对函数名字进行修改,将参数类型添加到最终名字中,在代码层面,看起来函数名字相同,但编译器编译完成之后,底层使用的名字不一样。int Add(int left,int right)->?Add@@HHH@Z,double Add(double left,double right)->?Add@@YANNN@Z

1.名字修饰:

在C语言中,名字修饰只是在函数名前加下划线,所以只要函数名相同,就会导致冲突(C语言不支持函数重载的原因)

在C++中,名字修饰是“?函数名@域名1@域名2@......@@参数列表@Z”的格式构成的,包含:

a、函数名

b、所在域

c、参数列表

所以C++中,以上三个必须完全相同,才会出现冲突,这就是函数重载的原理(底层处理)

2.extern“C”将一段代码包起来,那么这段代码将会以C语言的风格进行编译

五、引用(一般用于作参数或者返回值

引用是给一个(变量)起别名,两个名字都是一个变量,所以操作谁从结果上看都一样。共用一块内存空间。注意const是常量。

引用是代替指针完成跨栈操作的,所以它具备指针跨栈的一切特点。

引用的底层实现实际是指针

特点:引用在定义时必须初始化。

一个变量可以有多个引用。

一旦引用了一个变量,就不能再引用其他变量。

规避掉了野指针,空指针的过程。

void TestRef()
{ int a = 10;
int& ra = a;//<====定义引用类型
printf("%p\n", &a);
printf("%p\n", &ra);
}
int& Add(int a, int b)
{ int c = a + b; return c;
}
int main()
{ int& ret = Add(1, 2); Add(3, 4); cout << "Add(1, 2) is :"<< ret <<endl;return 0;
}

输出结果是7。函数返回时,离开函数作用域后,栈上空间已经还给系统了。不能用栈上空间作为引用类型返回。

C语言中,传值:不能通过形参改变外部的实参,因为形参是实参的一份拷贝。
          如果想要通过形参改变外部的实参,可以使用指针解引用。传地址。问题:指针可能不安全,--每次使用必须要指针判空,代码可读性较差。

2.引用和指针的不同点:

(1). 引用在定义时必须初始化,指针没有要求

(2). 引用在初始化时引用一个实体后,就不能再引用其他实体,而指针可以在任何时候指向任何一个同类型实体

(3). 没有NULL引用,但有NULL指针

(4). 在sizeof中含义不同引用结果为引用类型的大小,但指针始终是地址空间所占字节个数(32位平台下占4 个字节)

(5). 引用自加即引用的实体增加1,指针自加即指针向后偏移一个类型的大小

(6).没有多级引用,但有多级指针

(7). 访问实体方式不同,指针需要显式解引用,引用编译器自己处理

(8). 引用比指针使用起来相对更安全(指针可以改变指向,指针可以为空,野)

六、内联函数

调用时不创建新栈而直接在调用处展开的函数叫内联函数,关键字为inline(以空间换时间)

内联函数一个对编译器的建议,如果函数过于复杂,编译器是会不接受你的建议,而处理成普通的函数。

inline可以代替带参宏。

宏是不安全的,能用inline就用inline。inline安全

类声明中定义的函数除了虚函数的其他函数都会自动隐式地当成内联函数

编译器对 inline 函数的处理步骤

  1. 将 inline 函数体复制到 inline 函数调用点处;
  2. 为所用 inline 函数中的局部变量分配内存空间
  3. 将 inline 函数的的输入参数和返回值映射到调用方法的局部变量空间中;
  4. 如果 inline 函数有多个返回点将其转变为 inline 函数代码块末尾的分支(使用 GOTO)

虚函数(virtual)可以是内联函数(inline)吗?

  • 虚函数可以是内联函数,内联是可以修饰虚函数的,但是当虚函数表现多态性的时候不能内联。
  • 内联是在编译器建议编译器内联,而虚函数的多态性在运行期,编译器无法知道运行期调用哪个代码,因此虚函数表现为多态性时(运行期)不可以内联。
  • inline virtual 唯一可以内联的时候是:编译器知道所调用的对象是哪个类(如 Base::who()),这只有在编译器具有实际对象而不是对象的指针或引用时才会发生。

宏的优缺点? 优点: 1.增强代码的复用性。 2.提高性能。

缺点: 1.不方便调试宏。(因为预编译阶段进行了替换) 2.导致代码可读性差,可维护性差,容易误用。 3.没有类型安全的检查 。

C++有哪些技术替代宏? 1. 常量定义 换用const 2. 函数定义 换用内联函数

七、Auto关键字(c++11):

1.修饰具有自动存储器的局部变量,

2.使用auto定义变量时必须对其进行初始化,在编译阶段编译器需要根据初始化表达式来推导auto的实际类型。

3.主要用于范围for:用于遍历容器vector,数组)迭代的范围必须确定。

第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。

4.使用auto声明指针类型时,auto与auto*无区别,声明引用时必须加&。

5.auto不能作为函数参数,不能声明数组

八、范围for

for循环后的括号由冒号“ :”分为两部分:

          第一部分是范围内用于迭代的变量,第二部分则表示被迭代的范围。
          可用continue结束本次循环,可用break跳出整个循环。
          迭代范围必须确定,数组第一个元素和最后一个元素的范围。
          迭代对象要实现++,--操作。

九、指针空值nullptr

1.null被定义为字面常量0或者无类型指针的常量。nullptr代表指针空值常量。

2. 在使用nullptr表示指针空值时,不需要包含头文件,因为nullptr是C++11作为新关键字引入的。

3. 在C++11中,sizeof(nullptr) 与 sizeof((void*)0)所占的字节数相同。

4. 为了提高代码的健壮性,在后续表示指针空值时建议最好使用nullptr。

十、const作用

  1. 修饰变量,说明该变量不可以被改变;
  2. 修饰指针,分为指向常量的指针(pointer to const)和自身是常量的指针(常量指针,const pointer);
  3. 修饰引用,指向常量的引用(reference to const),用于形参类型,即避免了拷贝,又避免了函数对值的修改;
  4. 修饰成员函数,说明该成员函数内不能修改成员变量。

十一、volatile

volatile int i = 10; 
  • volatile 关键字是一种类型修饰符,用它声明的类型变量表示可以被某些编译器未知的因素(操作系统、硬件、其它线程等)更改。所以使用 volatile 告诉编译器不应对这样的对象进行优化。
  • volatile 关键字声明的变量,每次访问时都必须从内存中取出值(没有被 volatile 修饰的变量,可能由于编译器的优化,从 CPU 寄存器中取值)
  • const 可以是 volatile (如只读的状态寄存器)
  • 指针可以是 volatile

十二、类:类和对象

类是概念,对象是实体。

1、结构体在C和C++中的不同

(1).在C++中,用结构体定义变量 不需要加Struct,而C中不行。

(2).在C++中,可以定义空结构体,大小为1,而C中不行。

(3).在C++中,可以在结构体中声明甚至实现函数,在C中只能放函数指针。

(4).在C++中,成员函数直接可以访问本结构的成员变量而无需传入,在C中,函数和结构并无直接关联。

*成员函数不影响结构体的大小

*因为成员函数是放在公共区域的,只是在这个结构体域中而已。

2.类的定义:

12.类的定义:
class className
{
// 类体:由成员函数和成员变量组成
}; // 一定要注意后面的分号

访问限定符:public,protected,private

C++需要兼容C语言,所以C++中struct可以当成结构体去使用。另外C++中struct还可以用来定义类。

struct与class的区别:struct的成员默认访问方式是public,class是struct的成员默认访问方式是private。

类是一类特殊的结构体,在上述结构体中,加入public:,把struct改成class就成了一个简单的类。

面向对象的三大特性:封装、继承、多态。

访问限定符:

public:正常访问

private:能在类内访问

protected:目前跟private没有区别

tip1:访问限定符旨在编译阶段生效,编译好后,在运行阶段并没有限定。

tip2:结构体其实也能使用访问限定符,只是一般不去使用,因为它是用来兼容C结构体的。

tip3:结构体中默认是public,而类中默认是private

封装:将数据和操作数据的方法进行有机结合,隐藏对象的属性和实现细节,仅对外公开接口来和对象进行交互。本质上是一种管理,使用protected/private把成员封装起来,开放公有成员函数访问。

十三、类的作用域

类定义了一个新的作用域类的所有成员都在类的作用域中。在类体外定义成员,需要使用 :: 作用域解析符指明成员属于哪个类域。

如何计算类的大小:结构体的内存对齐原则

对齐规则是按照成员的声明顺序,依次安排内存,其偏移量为成员大小的整数倍,0看做任何成员的整数倍。

      1. 第一个成员在与结构体偏移量为0的地址处。
      2. 其他成员变量要对齐到某个数字(对齐数)的整数倍的地址处。
           注意:对齐数 = 编译器默认的一个对齐数 与 该成员大小的较小值。
            VS中默认的对齐数为8,gcc中的对齐数为4
     3. 结构体总大小为:最大对齐数(所有变量类型最大者与默认对齐参数取最小)的整数倍。
     4. 如果嵌套了结构体的情况,嵌套的结构体对齐到自己的最大对齐数的整数倍处,结构体的整体大小就是所有最大对齐数(含嵌套结构体的对齐数)的整数倍。

最后结构体的大小为最大成员的整数倍  (所以这里的B中,最后结构体大小为double的倍数是24,而不是18)。

另:C语言中,空的结构体在内存中所占大小为0。(仅在gcc中测试为0)。

例如:

typedef struct
{ int a; short b; double c;
}A;
typedef struct
{int a;double b;short c;
}B;
结果是sizeof(A)=16;sizeof(B)=24

一般不说明,都说得是32位编译器

 为什么要内存对齐?
1.平台原因(移植原因):并不是所有的硬件平台都能访问任意地址上的任意数据的;某些硬件平台只能在某些地址处取某些特定类型的数据,否则抛出硬件异常。
2.性能原因:数据结构(尤其是栈)应该尽可能地在自然边界上对齐。原因在于,为了访问未对齐的内存,处理器需要作两次内存访问;而对齐的内存访问仅需要一次访问。

大端和小端是什么?

大端存储模式:就是内存的低地址上存着数据的高位,高地址上存着数据的低位。

小端存储模式:就是内存的低地址上存数据的低位,而高地址上存数据的高位。

为什么要有大小端?

在计算机系统中,规定:每个地址单元都会对应一个字节(8个bit),但是,在c语言中,除了有一个字节(8个bit)的char,也有两个字节(16个bit)的short,也有四个字节(32个bit)的long(在不同的编译器下可能不同)。对于16位或者32位的处理器,即就是大于8位的处理器,由于寄存器的宽度大于一个字节,那么就存在如何将一个多字节的变量的数据如何存放的问题——所以,就有了大小端之分。

十四、this指针(本质是一个成员函数的形参)

this指针是成员函数第一个隐含的指针形参

一般成员函数中都含有一个this指针,这个指针指向调用这个成员函数的对象。

this指针的特性:

1. this指针的类型:类类型* const
2. 只能在“成员函数”的内部使用
3. this指针本质上其实是一个成员函数的形参,是对象调用成员函数时,将对象地址作为实参传递给this形参。所以对象中不存储this指针
4. this指针是成员函数第一个隐含的指针形参,一般情况由编译器通过ecx寄存器自动传递,不需要用户传递

十五、类的6个默认成员函数

十六、构造函数(初始化)

它是一个特殊的成员函数,在对象的生命周期内只调用一次。并不是开空间创造对象,而是初始化对象。

特性:

1. 函数名与类名相同。

              2. 无返回值。

              3. 对象实例化时编译器自动调用对应的构造函数。

              4. 构造函数可以重载。

系统会提供一个默认的构造函数(缺省),如果自己实现了构造函数,则系统不再提供默认的构造函数。

构造函数可以存在参数,它与其他的构造函数是以 函数重载的方式共同存在的

无参构造函数、全缺省构造函数、我们没写编译器默认生成的构造函数,都可以认为是默认成员函数。

再谈构造函数:初始化列表:引用成员变量、const成员变量

class Date
{public:Date(int year, int month, int day): _year(year) , _month(month), _day(day){} private: int _year;int _month; int _day;
};

类类型成员(无默认构造函数)

拷贝构造函数指的是参数为本类其他对象的引用的构造函数,它在给对象初始化成本类其他对象时调用。系统会自动提供一个拷贝构造函数。

十七、析构函数(释放/置0)

析构函数是当一个栈被销毁前调用的,在C++中,当一个函数被销毁前会调用栈中每一个对象的析构函数。类似于C语言中Destory函数

其特征如下:

1. 析构函数名是在类名前加上字符 ~

2. 无参数无返回值。

3. 一个类有且只有一个析构函数。若未显式定义,系统会自动生成默认的析构函数(缺省)

4. 对象生命周期结束时,C++编译系统系统自动调用析构函数。

十八、拷贝构造函数

只有单个形参,该形参是类类型对象的引用(常用const修饰),在用已存在的类类型对象
创建新对象时由编译器自动调用。

1. 拷贝构造函数是构造函数的一个重载形式。

2. 拷贝构造函数的参数只有一个且必须使用引用传参,使用传值方式会引发无穷递归调用。

3.默认的拷贝构造函数对象按内存存储按字节序完成拷贝,叫做浅拷贝。

十九、运算符重载(增强代码可读性)

operator加上要重载的符号+、=等等。不改变运算符的优先级、操作数个数等等。

.* 、:: 、sizeof 、?: 、. 注意以上5个运算符不能重载。

1.运算符重载:将运算符看成函数,把他的几目当成参数,通过参数的类型识别出对应的操作方法。也是多肽的一种体现。相当于函数重载。

运算符重载有指定的规则,规则根据运算符来制定。

2.赋值运算符重载

(1). 参数类型

(2). 返回值

(3). 检测是否自己给自己赋值

(4). 返回*this

(5). 一个类如果没有显式定义赋值运算符重载,编译器也会生成一个,完成对象按字节序的值拷贝。

二十、const成员函数

1.const加在成员函数的末尾,代表这个函数中的this是const修饰的

2.const对象或者 (成员函数)可以调用其他const函数(const成员函数),

非const对象或者(成员函数)可以调用非const成员函数和const成员函数(const成员函数)。

3.将const修饰的类成员函数称之为const成员函数,const修饰类成员函数,实际修饰该成员函数隐含的this指针,不能对类的任何成员进行修改。

二十一、静态成员

静态成员跟类走不跟对象走,类在它在。而一般成员是对象在他才在。所以静态成员可以通过类名直接调用,而普通成员必须通过对象调用。

静态成员也有private,public,protected之分

1、静态成员变量

(1)所有对象共享,无论谁改了,所有的一起改。

(2)存储在全局区,不占用类的空间,所以取sizeof的时候不算在内。

赋初值只能在类外,赋值时不加static,用“类名::变量名=n 直接赋值”

(3)不需要对象,可以直接用类名::调用

2、静态成员函数(在类外进行初始化)

1只能直接访问静态成员变量,无法访问其他的普通成员,因为他没有this指针

二十二、友元函数

友元就是让一个外部函数或者外部类能访问我的私有成员

友元打破了原有的权限制度,所以十分危险,不建议使用

需要在类的内部声明,声明时需要加friend关键字。

说明:

(1)友元函数可访问类的私有成员,但不是类的成员函数

(2)友元函数不能用const修饰

(3)友元函数可以在类定义的任何地方声明,不受类访问限定符限制

(4)一个函数可以是多个类的友元函数

(5)友元函数的调用与普通函数的调用和原理相同

二十三、内部类

在另一个类内声明的类,它不属于外部类,调用需要使用域操作运算(::)符,同时它有权限的限制。(private的类声明不能直接在外面声明对象)内部类是外部类的友元,外部类不是内部类的友元。

特性:

1. 内部类可以定义在外部类的public、protected、private都是可以的。

2. 注意内部类可以直接访问外部类中的static、枚举成员,不需要外部类的对象/类名。

3. sizeof(外部类)=外部类,和内部类没有任何关系

二十四、static

静态的成员变量一定要在类外进行初始化

特性:

            1. 静态成员为所有类对象所共享,不属于某个具体的实例
            2. 静态成员变量
必须在类外定义,定义时不添加static关键字
            3. 类静态成员即可用类名::静态成员或者对象.静态成员来访问
            4. 静态成员函数
没有隐藏的this指针,不能访问任何非静态成员
            5. 静态成员和类的普通成员一样,也有public、protected、private3种访问级别,也可以具有返回值,const修饰符等参数

  1. 修饰普通变量,修改变量的存储区域和生命周期,使变量存储在静态区,在 main 函数运行前就分配了空间,如果有初始值就用初始值初始化它,如果没有初始值系统用默认值初始化它。
  2. 修饰普通函数表明函数的作用范围,仅在定义该函数的文件内才能使用。在多人开发项目时,为了防止与他人命名空间里的函数重名,可以将函数定位为 static。
  3. 修饰成员变量修饰成员变量使所有的对象只保存一个该变量,而且不需要生成对象就可以访问该成员。
  4. 修饰成员函数修饰成员函数使得不需要生成对象就可以访问该函数,但是在 static 函数内不能访问非静态成员。

一文带你入门C++,类与对象、6个默认成员函数、this指针以及static关键字。相关推荐

  1. C++_类中的6个默认成员函数

    C++类中有6个默认函数,分别是:构造函数.析构函数.拷贝构造函数.赋值运算符重载.取地址及const取地址运算符重载.这六个函数是很特殊的函数,如果我们不自己实现,编译器就会自己实现.这篇博客中将一 ...

  2. 怎么将一个类的成员函数作为指针传递给另一个类的成员函数

    今天帮同学解决了一个问题,怎么把一个类的成员函数作为指针传递给另一个类的成员函数. 以前只接触过C语言中的函数指针: #include <iostream.h> void add(int ...

  3. C++类与对象(二) 六个默认成员函数

    目录 1.构造函数 (1)引入 (2)性质与验证 (3)总结 2.析构函数 (1)概念 (2)性质与验证 (3)总结 3拷贝构造函数 (1)概念 (2)性质与验证 (3)总结 4.赋值运算符重载 (1 ...

  4. 一文带你入门go语言

    一文带你入门go语言 go/golang是一门google开发的编程语言,其代码简洁易读,天生支持并发,完美契合当今互联网生态. 目前Go语言已经⼴泛应用于人工智能.云计算开发.容器虚拟化.⼤数据开发 ...

  5. 一文带你入门flink sql

    文章目录 一文带你入门flink sql 写在前面 环境准备 正文 遇到的一些问题 错误一 错误二 错误三 一文带你入门flink sql 写在前面 本次实战主要是通过Flink SQL Client ...

  6. java etl工具_一文带你入门ETL工具-datax的简单使用

    什么是ETL? ETL负责将分布的.异构数据源中的数据如关系数据.平面数据文件等抽取到临时中间层后进行清洗.转换.集成,最后加载到数据仓库或数据集市中,成为联机分析处理.数据挖掘的基础. ETL是数据 ...

  7. 学习笔记:C++初阶【C++入门、类和对象、C/C++内存管理、模板初阶、STL简介、string、vector、list、stack、queueu、模板进阶、C++的IO流】

    文章目录 前言 一.C++入门 1. C++关键字 2.命名空间 2.1 C语言缺点之一,没办法很好地解决命名冲突问题 2.2 C++提出了一个新语法--命名空间 2.2.1 命名空间概念 2.2.2 ...

  8. python入门笔记——类和对象⑤(继承)

    python入门笔记--类和对象⑤(继承) # python中展现面向对象的三大特征:封装.继承.多态 # 封装:把内容封装到某个地方以便后续的使用 # 对封装来说,其实就是使用初始化构造方法将内容封 ...

  9. 【C++从入门到踹门】第三篇:类和对象(中)类的默认成员函数

    目录 1.类的默认成员函数 2.构造函数 2.1 构造函数引入 2.2 构造函数概念及特点 3. 析构函数 3.1 析构函数引入 3.2 析构函数的概念 3.3 在哪些情况下会程序会执行析构函数? 3 ...

最新文章

  1. python【力扣LeetCode算法题库】1111- 有效括号的嵌套深度
  2. linux 新增网络接口,为网络接口添加多IP - CentOS 7系统配置上的变化解析_Linux教程_Linux公社-Linux系统门户网站...
  3. 【Linux网络编程】UDP编程
  4. 数字图像处理之空间域图像增强
  5. C++拷贝构造函数的调用时机
  6. c语言如何判断是否是子序列,leetcode392(判断子序列)--C语言实现
  7. JS 获取浏览器窗口大小
  8. Oracle Parallel Execution(并行执行)
  9. 基于centos6.5 hbase 集群搭建
  10. 常用SNS开源交友系统比较
  11. 在Linux操作系统下修改IP、DNS和路由配置
  12. canvas绘制圆和弧(三)
  13. sqlmap安装(python2或python3都行)
  14. 智慧校园市场调研与解决方案落地规划
  15. 智力题解题报告 No.4 9个点10个线
  16. NC6 转库单、采购入库单、库存委托入库单签字后自动生成调拨订单
  17. 第三方士大夫士大夫士大夫士大夫士大夫
  18. 校企同游快乐工作——湖南工程职院美和易思教师开展素质拓展活动
  19. unity中AO、metallic、roughness贴图的使用方式
  20. 使用Google浏览器做真机页面调试

热门文章

  1. torchtext处理文本数据——将文本变为embedding(学习三)
  2. 聚类效果评价——Davies-Bouldin(DB指标)——内部评估标准(3)
  3. Auto ARIMA 逐个时间点预测
  4. aotuwried是java的注解吗_@autowire注入为null
  5. java反射 获取变量值_Java反射:如何获取变量的名称?
  6. mysql触发器的基本操作_MySQL基本操作-触发器
  7. stm32-再谈GPIO
  8. Unix系统编程()信号处理器简介
  9. Oracle安装-------实例化EM 配置文件时出错问题 ( 转 )
  10. 《软件工程》课程改进意见