目录
C++继承了所有的C特性, 在C的基础上提供了更多的语法和特性 如:面向对象支持,类型加强,函数加强,异常处理 ...
C++的设计目标是运行效率与开发效率的统一, C++更强调语言的实用性,所有的变量都可以在需要时候再定义
register关键字的加强
register关键字请求编译器将局部变量存储于寄存器中
在C++中依然支持register关键字,C++编译器有自己的优化方式,C语言中无法获取register变量的地址,C++中可以获得register变量的地址
现代C++编译器发现程序中需要取register变量的地址时,register对变量的声明变得无效,完全是因为要兼容C语言
早期C语言编译器不会对代码进行优化,因此register变量是一个很好的补充
全局变量的加强
在C语言中,重复定义多个同名的全局变量是合法的,C语言中多个同名的全局变量最终会被链接到全局数据区的同一个地址空间上
在C++中,不允许定义多个同名的全局变量
#include <stdio.h>
int g_v;
//int g_v;
int main(int argc, char *argv[])
{
printf("Begin...\n");
int c = 0;
for(int i = 1; i <= 3; i++)
{
for(int j = 1; j <= 3; j++)
{
c += i * j;
}
}
printf("c = %d\n", c);
register int a = 0;
printf("&a = %p\n", &a);
printf("End...\n");
return 0;
}
C++:
当取消注释全局变量会发现报错
C:
不能获取寄存器变量的地址
“for”循环初始声明只能在C99或C11模式下进行,允许同名全局变量 ,体现了C++比C更加严格
类型加强
int f( ) 与 int f( void ) 有区别吗?如果有区别是什么?
C++中所有的标识符都必须显式的声明类型,C语言中的默认类型在C++中是不合法的
在C语言中:int f()表示返回值为int, 接受任意参数的函数,f(void)表示返回值为int的无参函数
在C++中:int f()和int f(void)具有相同的意义,表示函数返回值为int的无参函数
struct关键字的加强
C语言中的struct定义了一组变量的集合,使用时还得加上struct
C++中的struct用于定义一个全新的类型,可以直接定义变量
#include <stdio.h>
struct Student
{
const char* name;
int age;
};
f( i )
{
printf("i = %d\n", i);
}
g()
{
return 5;
}
int main(int argc, char *argv[])
{
struct Student s1 = {"wss", 30};
struct Student s2 = {"nyist", 30};
f(10);
printf("g() = %d\n", g(1, 2, 3, 4, 5));
return 0;
}
毫无疑问C++编译不通过, C++绝不允许默认类型
内联函数
C++中的const常量可以替代宏常数定义,如: const int A = 3; ≈ #define A 3
C++中推荐使用内联函数替代宏代码片段 ,C++中使用 inline 关键字声明内联函数
内联函数声明时inline关键字必须和函数定义结合在—起,否则编译器会直接忽略内联请求。
C++编译器直接将函数体插入函数调用的地方 ,内联函数没有普通函数调用时的额外开销(压栈,跳转,返回)
C++编译器不—定满足函数的内联请求!
#include <stdio.h>
#define FUNC(a, b) ((a) < (b) ? (a) : (b))
inline int func(int a, int b)
{
return a < b ? a : b;
}
int main(int argc, char *argv[])
{
int a = 1;
int b = 3;
int c = FUNC(++a, b); // int c = func(++a, b);
printf("a = %d\n", a);
printf("b = %d\n", b);
printf("c = %d\n", c);
return 0;
}
使用宏易错,当改为使用内联函数
VS2015
不难看出发生了函数调用,内联未成功,设置编译器
内联成功,即内联函数可能被拒绝!!!
内联函数具有普通函数的特征(参数检查,返回类型等) ,函数的内联请求可能被编译器拒绝
函数被内联编译后,函数体直接扩展到调用的地方 ,宏代码片段由预处理器处理,进行简单的文本替换,没有任何编译过程,因此可能出现副作用
现代C++编译器能够进行编译优化,一些函数即使没有 inline声明,也可能被内联编译
一些现代C++编译器提供了扩展语法,能够对函数进行强制内联(但也不一定),如:
- g++: __attribute__((always_inline))
- MSVC : __forceinline
C++中inline内联编译的限制:
不能存在任何形式的循环语句 ,不能存在过多的条件判断语句 ,函数体不能过于庞大
不能对函数进行取址操作 ,函数内联声明必须在调用语局句之前
函数参数的扩展
函数参数的默认值
C++中可以在函数声明时为参数提供—个默认值 ,当函数调用时没有提供参数的值,则使用默认值
参数的默认值必须从右向左提供 ,函数调用时使用了默认值,则后续参数必须使用默认值
#include <stdio.h>
int add(int x, int y = 0, int z = 0); // 参数的默认值必须在函数声明中指定且函数定义不能指定
int main(int argc, char *argv[])
{
printf("%d\n", add(1)); // 1
printf("%d\n", add(1, 2)); // 3
printf("%d\n", add(1, 2, 3)); // 6
return 0;
}
int add(int x, int y, int z)
{
return x + y + z;
}
函数占位参数
在C++中可以为函数提供占位参数 ,占位参数只有参数类型声明,而没有参数名声明 ,一般情况下,在函数体内部无法使用占位参数
占位参数与默认参数结合起来使用 ,兼容C语言程序中可能出现的不规范写法 。例如: void func(); ←→ void func(void);
#include <stdio.h>
// int func(int x, int)
int func(int x, int = 0);
int main(int argc, char *argv[])
{
printf("%d\n", func(1)); // 1
printf("%d\n", func(2, 3)); // 2
return 0;
}
int func(int x, int)
{
return x;
}
C++中的命名空间 (namespace)
在C语言中只有—个全局作用域 ,C语言中所有的全局标识符共享同—个作用域 ,标识符之间可能发生冲突
C++中提出了命名空间的概念 ,命名空间将全局作用域分成不同的部分 ,不同命名空间中的标识符可以同名而不会发生冲突
命名空间可以相互嵌套 ,全局作用域也叫默认命名空间
C++命名空间的定义 namespace Name { }
使用整个命名空间: using namespace name;
使用命名空间中的变量: using name::variable;
使用默认命名空间中的变量:::variable
#include <stdio.h>
namespace First
{
int i = 0;
}
namespace Second
{
int i = 1;
namespace Internal
{
struct P
{
int x;
int y;
};
}
}
int main()
{
using namespace First;
using Second::Internal::P; //using namespace Secomd::Internal;
printf("First::i = %d\n", i);
printf("Second::i = %d\n", Second::i);
P p = {2, 3};
printf("p.x = %d\n", p.x);
printf("p.y = %d\n", p.y);
return 0;
}
C++中的函数重载
函数重载:用同—个函数名定义不同的函数 , 当函数名和不同的参数搭配时函数的含义不同 , 函数重载必然发生在同一个作用域
函数重载至少满足其中一个条件:参数个数不同、参数类型不同 、参数顺序不同
#include <stdio.h>
#include <string.h>
int func(int x)
{
return x;
}
int func(int a, int b)
{
return a + b;
}
int func(const char* s)
{
return strlen(s);
}
int main(int argc, char *argv[])
{
printf("%d\n", func(3));
printf("%d\n", func(4, 5));
printf("%d\n", func("D.T.Software"));
return 0;
}
当函数默认参数遇上函数重载会出现对函数调用的不明确,如:int func(int a, int b, int c = 0) 和 int func(int a, int b) -> func(1, 2);
C++引入了太多特性,然而有些特性之间会产生冲突
编译器调用重载函数的准则
将所有同名函数作为候选者 , 尝试寻找可行的候选函数 , 精确匹配实参 , 通过默认参数能够匹配实参 , 通过默认类型转换匹配实参
若最终寻找到的候选函数不唯—,则出现二义性,编译失败。 无法匹配所有候选者,函数未定义,编译失败。
函数重载的注意事项
重载函数在本质上是相互独立的不同函数 ,重载函数的函数类型不同
函数返回值不能作为函数重载的依据 ,函数重载是由函数名和参数列表决定的!
#include <stdio.h>
int add(int a, int b) // int(int, int)
{
return a + b;
}
int add(int a, int b, int c) // int(int, int, int)
{
return a + b + c;
}
int main()
{
printf("%p\n", (int(*)(int, int))add);
printf("%p\n", (int(*)(int, int, int))add);
return 0;
}
即对C++来说重载函数是不同的函数,是经过名称修饰的函数,不同的编译器厂商的名称修饰方法可能不同
删除重载函数后,变为C代码,发现没有名称修饰
函数重载遇上函数指针
将重载函数名赋值给函数指针时 ,根据重载规则挑选与函数指针参数列表—致的候选者 ,严格匹配候选者的函数类型与函数指针的函数类型
// 函数重载遇上函数指针 test.cpp
#include <stdio.h>
#include <string.h>
int func(int x)
{
return x;
}
int func(int a, int b)
{
return a + b;
}
int func(const char* s)
{
return strlen(s);
}
typedef int (*PFUNC) (int a); // 修改为 typedef void (*PFUNC) (int a); 会报错
int main(int argc, char *argv[])
{
int c = 0;
PFUNC p = func;
c = p(1);
printf("c = %d\n", c);
return 0;
}
可以通过函数指针类型获得重载函数的地址,如:(int(*)(int, int))func
函数重载必然发生在同—个作用域中 ,编译器需要用参数列表或函数类型进行函数选择 ,无法直接通过函数名得到重载函数的入口地址
类中的成员函数可以进行重载
构造函数的重载 ,普通成员函数的重载 ,静态成员函数的重载
全局函数,普通成员函数以及静态成员函数之间不能构成重载,函数重载必须发生在同一个作用域中,C++中函数名经名称修饰后会加上作用域(如名称空间,类名)等其他符号
#include <stdio.h>
class Test
{
int i;
public:
Test()
{
printf("Test::Test()\n");
this->i = 0;
}
Test(int i)
{
printf("Test::Test(int i)\n");
this->i = i;
}
Test(const Test& obj)
{
printf("Test(const Test& obj)\n");
this->i = obj.i;
}
static void func()
{
printf("void Test::func()\n");
}
void func(int i)
{
printf("void Test::func(int i), i = %d\n", i);
}
int getI()
{
return i;
}
};
void func()
{
printf("void func()\n");
}
void func(int i)
{
printf("void func(int i), i = %d\n", i);
}
int main()
{
func();
func(1);
Test t; // Test::Test()
Test t1(1); // Test::Test(int i)
Test t2(t1); // Test(const Test& obj)
func(); // void func()
Test::func(); // void Test::func()
func(2); // void func(int i), i = 2;
t1.func(2); // void Test::func(int i), i = 2
t1.func(); // void Test::func()
return 0;
}
重载的意义
通过函数名对函数功能进行提示 ,通过参数列表对函数用法进行提示 ,扩展系统中已经存在的函数功能
#include <stdio.h>
#include <string.h>
char* strcpy(char* buf, const char* str, unsigned int n)
{
return strncpy(buf, str, n);
}
int main()
{
const char* s = "Nyist520";
char buf[8] = {0};
//strcpy(buf, s);
strcpy(buf, s, sizeof(buf)-1);
printf("%s\n", buf);
return 0;
}
C/C++代码相互调用
实际工程中C++和C代码相互调用是不可避免的 ,C++编译器能够兼容C语言的编译方式 ,但C++编译器会优先使用C++编译的方式
C++编译器不能以C的方式编译重载函数,编译方式决定函数名被编译后的目标名
C++编译方式将函数名和参数列表编译成目标名 (包括作用域),C编译方式只将函数名作为目标名进行编译
extern关键字能强制让C++编译器进行C方式的编译 ,extern "C"坚决不能出现重载函数
extern "C"
{
//do C-style compilation here
}
编程实验:C++调用C函数
// add.h
int add(int a, int b);
// add.c
#include "add.h"
int add(int a, int b)
{
return a + b;
}
// main.cpp
#include <stdio.h>
extern "C"
{
#include "add.h"
}
int main()
{
int c = add(1, 2);
printf("c = %d\n", c);
return 0;
}
gcc生成编译C风格的函数生成 add.o
g++使用add.o
g++编译器可以成功编译main.cpp和add.o。而不加extern, g++编译器报错(C/C++对函数名处理不同)
如何保证—段C代码只会以C的方式被编译?
__cplusplus是C++编译器内置的标准宏定义 ,__cplusplus的值在不同版本C++中不同如:VS默认为199711L
__cplusplus的重要意义 :确保C代码以统—的C方式被编译成目标文件
the value of the macro is
199711L
for the 1998 C++ standard,201103L
for the 2011 C++ standard,201402L
for the 2014 C++ standard,201703L
for the 2017 C++ standard, or an unspecified value strictly larger than201703L
for the experimental languages enabled by -std=c++2a and -std=gnu++2a.
#ifdef __cplusplus
extern "C"
{
#endif
//C-Style Compilation
#include "add.h"
#ifdef __cplusplus
}
#endif
C调用C++函数
若为普通函数直接在C++函数声明前加 extern "C"
若为成员函数需要包装,将成员函数的调用封装在普通成员函数中再加 extern "C"
若调用的函数为重载函数,可以提供多个包装(可以通过函数指针类型获得重载函数的地址)
// C++ Code
extern "C" void func();
void func()
{
printf("这里是C++函数");
}
// C Code
extern void func();
func();
// C++ Code
class Test
{
public:
void print()
{
printf("这里是成员函数");
}
};
extern "C" void print(Test* t)
{
t->print();
}
// C Code
Test* p = NULL;
print(p);
进阶面向对象
面向对象的意义在于将日常生活中习惯的思维方式引入程序设计中,将需求中的概念直观的映射到解决方案中,以模块为中心构建可复用的软件系统 ,提高软件产品的可维护性和可扩展性
类:指的是一类事物,是一个抽象的概念 ,对象:指的是属于某个类的具体实体
类是一种模型,这种模型可以创建出不同的对象实体 ,对象实体是类模型的一个具体实例 ,一个类可以有很多对象,而—个对象必然属于某个类。
类用于抽象的描述一类事物所特有的属性和行为 ,对象是具体的事物,拥有所属类中描述的一切属性和行为
类之间的基本关系
继承 :从已存在类细分出来的类和原类之间具有继承关系(is-a) ,继承的类(子类)拥有原类(父类)的所有属性和行为
组合 :一些类的存在必须依赖于其它的类,这种关系叫组合(has-a),组合的类在某一个局部上由其它的类组成
#include <stdio.h>
struct Biology {
bool living;
};
struct Animal : Biology {
bool movable;
void findFood() { }
};
struct Plant : Biology {
bool growable;
};
struct Beast : Animal {
void sleep() { }
};
struct Human : Animal {
void sleep() { }
void work() { }
};
int main()
{
return 0;
}
类通常分为以下两个部分 :类的实现细节,类的使用方式 ,当使用类时,不需要关心其实现细节 ,当创建类时,才需要考虑其内部实现细节
C++中类的封装
成员变量: C++中用于表示类属性的变量 ,成员函数: C++中用于表示类行为的函数
C++中可以给成员变量和成员函数定义访问级别
- public :成员变量和成员函数可以在类的内部和外界访问和调用
- private :成员变量和成员函数只能在类的内部被访问和调用
- protected:父类的protected成员不能被外界访问但可以被子类(内部)访问
#include <stdio.h>
struct Biology
{
bool living;
};
struct Animal : Biology
{
bool movable;
void findFood()
{
}
};
struct Plant : Biology
{
bool growable;
};
struct Beast : Animal
{
void sleep()
{
}
};
struct Human : Animal
{
void sleep()
{
printf("I'm sleeping...\n");
}
void work()
{
printf("I'm working...\n");
}
};
struct Girl : Human
{
private:
int age; // 类的内部可以访问
int weight;
public:
void print() // 外界和类内部可访问
{
age = 22;
weight = 48;
printf("I'm a girl, I'm %d years old.\n", age);
printf("My weight is %d kg.\n", weight);
}
};
struct Boy : Human
{
private:
int height; // 类内部可访问
int salary;
public:
int age; // 外界和类内部可访问
int weight;
void print() // 外界和类内部可访问
{
height = 175;
salary = 9000;
printf("I'm a boy, my height is %d cm.\n", height);
printf("My salary is %d RMB.\n", salary);
}
};
int main()
{
Girl g;
Boy b;
g.print();
b.age = 19;
b.weight = 120;
//b.height = 180; // error
b.print();
return 0;
}
类成员的作用域
类成员的作用域都只在类的内部,外部无法直接访问 ,成员函数可以直接访问成员变量和调用成员函数 ,类的外部可以通过类变量访问public成员 ,类成员的作用域与访问级别没有关系
class的引入
struct在C语言中已经有了自己的含义,必须继续兼容 ,在C++中提供了新的关键字class用于类定义
class和struct的用法是完全相同的 ,在用struct定义类时,所有成员的默认访问级别为public ,在用class定义类时,所有成员的默认访问级别为private
#include <stdio.h>
struct A
{
// defualt to public
int i;
// defualt to public
int getI()
{
return i;
}
};
class B
{
// defualt to private
int i;
// defualt to private
int getI()
{
return i;
}
};
int main()
{
A a;
B b;
a.i = 4;
printf("a.getI() = %d\n", a.getI());
b.i = 4; //error
printf("b.getI() = %d\n", b.getI()); //error
return 0;
}
C++中的类支持声明和实现的分离
.h头文件中只有类的声明,成员变量和成员函数的声明 ,.cpp源文件中完成类的其它实现,成员函数的具体实现
实例分析
Operator类的分析 Operator.h Operator.cpp test.cpp
// Operator.h
#ifndef _OPERATOR_H_
#define _OPERATOR_H_
class Operator
{
private:
char mOp;
double mP1;
double mP2;
public:
bool setOperator(char op);
void setParameter(double p1, double p2);
bool result(double& r);
};
#endif
// Operator.cpp
#include "Operator.h"
bool Operator::setOperator(char op)
{
bool ret = false;
if( (op == '+') || (op == '-') || (op == '*') || (op == '/') )
{
ret = true;
mOp = op;
}
else
{
mOp = '\0';
}
return ret;
}
void Operator::setParameter(double p1, double p2)
{
mP1 = p1;
mP2 = p2;
}
bool Operator::result(double& r)
{
bool ret = true;
switch( mOp )
{
case '/':
if( (-0.000000001 < mP2) && (mP2 < 0.000000001) )
{
ret = false;
}
else
{
r = mP1 / mP2;
}
break;
case '+':
r = mP1 + mP2;
break;
case '*':
r = mP1 * mP2;
break;
case '-':
r = mP1 - mP2;
break;
default:
ret = false;
break;
}
return ret;
}
// test.cpp
#include <stdio.h>
#include "Operator.h"
int main()
{
Operator op;
double r = 0;
op.setOperator('/');
op.setParameter(9, 3);
if( op.result(r) )
{
printf("r = %lf\n", r);
}
else
{
printf("Calculate error!\n");
}
return 0;
}