问题
问题 1:c++头文件为什么没有.h?
在 c 语言中头文件使用扩展名.h,将其作为一种通过名称标识文件类型的简单方式。但是 c++得用法改变了,c++头文件没有扩展名。但是有些 c 语言的头文件被转换为 c++的头文件,这些文件被重新命名,丢掉了扩展名.h(使之成为 c++风格头文件),并在文件名称前面加上前缀 c(表明来自 c 语言)。例如 c++版本的 math.h 为 cmath.
由于 C 使用不同的扩展名来表示不同文件类型,因此用一些特殊的扩展名(如 hpp 或 hxx)表示 c++的头文件也是可以的,ANSI/IOS 标准委员会也认为是可以的,但是关键问题是用哪个比较好,最后一致同意不适用任何扩展名。
头文件类型 |
约定 |
示例 |
说明 |
c++旧式风格 |
以.h 结尾 |
iostream.h |
c++程序可用 |
c 旧式风格 |
以.h 结尾 |
math.h |
c/c++程序可用 |
c++新式风格 |
无扩展名 |
iostream |
c++程序可用,使用 namespace std |
转换后的 c |
加上前缀 c,无扩展名 |
cmath |
c++程序可用,可使用非 c 特性,如 namespace std |
问题 2:using namespace std 是什么?
namespace 是指标识符的各种可见范围。命名空间用关键字 namespace 来定义。命名空间是 C++的一种机制,用来把单个标识符下的大量有逻辑联系的程序实体组合到一起。此标识符作为此组群的名字。
问题 3:cout 、endl 是什么?
cout 是 c++中的标准输出流,endl 是输出换行并刷新缓冲区。
面向对象三大特性
封装
继承
多态
- 多态性可以简单地概括为“一个接口,多种方法”,字面意思为多种形态。程序在运行时才决定调用的函数,它是面向对象编程领域的核心概念。
C++对 C 的扩展
::作用域运算符
通常情况下,如果有两个同名变量,一个是全局变量,
另一个是局部变量,那么局部变量在其作用域内具有较高的优先权,
它将屏蔽全局变量。
1 2 3 4 5 6 7 8 9 10
| int a = 10;
void test(){ int a = 20; cout << "局部变量a:" << a << endl; cout << "全局变量a:" << ::a << endl; }
|
作用域运算符可以用来解决局部变量与全局变量的重名问题,
即在局部变量的作用域内,可用::对被屏蔽的同名的全局变量进行访问。
C++命名空间(namespace)
命名空间的用途:解决名称冲突
1 2 3 4 5 6 7 8 9 10
| void test(){ namespace A{ int a = 10; } namespace B{ int a = 20; } cout << "A::a : " << A::a << endl; cout << "B::a : " << B::a << endl; }
|
命名空间可以嵌套
1 2 3 4 5 6 7 8 9
| namespace A{ int a = 10; namespace B{ int a = 20; } } void test(){ cout << "A::a : " << A::a << endl; cout << "A::B::a : " << A::B::a << endl;
|
命名空间下可以存储 变量, 函数,结构体,类…..
1 2 3 4 5 6 7
| namespace A { int m_A = 10; void func(){} struct Person{}; class Animal{}; }
|
1 2 3 4 5 6
| namespace MySpace{ void func2(int param); } void MySpace::func2(int param){ cout << "MySpace::func2 : " << param << endl; }
|
命名空间必须声明在全局作用域下
命名空间是开放的,可以随时向空间中添加新成员
1 2 3 4 5 6
| namespace B{ int a = 20; } namespace B{ int b = 20; }
|
命名空间可以是匿名的,只能在本文件内访问
1 2 3 4 5 6 7
| namespace { int a = 20; int b = 20; } cout << a; cout << ::b;
|
命名空间可以起别名
1 2 3 4 5 6 7
| namespace veryLongName{
int a = 10; void func(){ cout << "hello namespace" << endl; } } namespace shortName = veryLongName; cout << "veryLongName::a : " << shortName::a << endl;
|
using
using 声明
1 2 3 4 5 6 7 8 9 10 11
| namespace LOL { int sunWuKongId = 1; } void test01() { using LOL::sunWuKongId; cout << sunWuKongId <<endl; }
|
using 编译指令
1 2 3 4
| using namespace LOL; cout << sunWuKongId <<endl;
|
1 2 3 4 5
| using namespace LOL; using namespace KingGlory; cout << LOL::sunWuKongId <<endl;
|
内联函数
内联函数引出
在 c 中我们经常把一些短并且执行频繁的计算写成宏,而不是函数,这样做的理由是为了执行效率,宏可以避免函数调用的开销,这些都由预处理来完成。
但是在 c++出现之后,使用预处理宏会出现两个问题:
为了保持预处理宏的效率又增加安全性,而且还能像一般成员函数那样可以在类里访问自如,c++引入了内联函数(inline function).
内联函数为了继承宏函数的效率,没有函数调用时开销,然后又可以像普通函数那样,可以进行参数,返回值类型的安全检查,又可以作为成员函数。
预处理宏的缺陷
问题一:需要加括号保证运算的完整
1 2 3 4 5 6 7 8 9 10 11 12 13
| #define ADD(x,y) x+y inline int Add(int x,int y){ return x + y; } void test(){ int ret1 = ADD(10, 20) * 10; int ret2 = Add(10, 20) * 10; cout << "ret1:" << ret1 << endl; cout << "ret2:" << ret2 << endl; }
#define ADD(x,y) ((x) + (y))
|
问题二:即使加了括号,有些情况依然与预期效果不符
1 2 3 4 5 6 7 8 9 10 11
| #define COMPARE(x,y) ((x) < (y) ? (x) : (y)) int Compare(int x,int y){ return x < y ? x : y; } void test02(){ int a = 1; int b = 3; cout << "Compare(int x,int y):" << Compare(++a, b) << endl; }
|
内联函数基本概念
在 c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。
- 在普通函数(非成员函数)函数前面加上 inline 关键字使之成为内联函数。
- 但是必须注意必须函数体和声明结合在一起,否则编译器将它作为普通函数来对待。
1
| inline void func(int a);
|
以上写法没有任何效果,仅仅是声明函数,应该如下方式来做:
1
| inline int func(int a){return ++;}
|
总结:
内联函数优点
解决了宏的缺陷,因为是普通函数,又带来了宏的优点,以空间换时间
关键字 inline
函数的声明和函数的实现同时拥有 inline 才算内联
类内部的内联函数
类内部的成员函数,都隐藏的加了 inline 关键字
任何在类内部定义的函数自动成为内联函数。
1 2 3 4 5
| class Person{ public: Person(){ cout << "构造函数!" << endl; } void PrintPerson(){ cout << "输出Person!" << endl; } }
|
内联函数和编译器
c++内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译:
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取址操作
内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数。
引用
1 2
| int a = 5; int& ref = a;
|
ref 并不是一个变量,只是 a 的别名,并不占用内存空间
引用很容易与指针混淆,它们之间有三个主要的不同:
- 不存在空引用。引用必须连接到一块合法的内存。
- 一旦引用被初始化为一个对象,就不能被指向到另一个对象。指针可以在任何时候指向到另一个对象。
- 引用必须在创建时被初始化。指针可以在任何时间被初始化。
把引用作为参数
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26
| #include <iostream> using namespace std;
void swap(int& x, int& y);
int main () { int a = 100; int b = 200;
swap(a, b);
return 0; }
void swap(int& x, int& y) { int temp; temp = x; x = y; y = temp;
return; }
|
字符串
stringstream 用法
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <iostream> #include <sstream>
using namespace std;
int main() { string a, b, c;
getline(cin, a); cin >> b >> c;
stringstream ssin(a); string str; while (ssin >> str) if (str == b) cout << c << ' '; else cout << str << ' '; return 0; }
|
stringstream ssin(a); 将字符串初始化成字符串流
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| #include <string> #include <iostream> #include <sstream>
int main () {
std::stringstream ss;
ss << 100 << ' ' << 200;
int foo,bar; ss >> foo >> bar;
std::cout << "foo: " << foo << '\n'; std::cout << "bar: " << bar << '\n';
return 0; }
|
Output:
foo: 100
bar: 200
从 string 对象 str 中读取字符。遇空格结束
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| #include <iostream> #include <sstream>
using namespace std;
int main() { string str = "hello world"; cout << str << endl;
stringstream ss(str); string abc; while(ss >> abc) { cout << abc <<endl; }
return 0; }
|
OUTPUT:
hello world
hello
world
结构体
若使用普通的变量来存储的话,数据是分散的。
1 2 3 4
| char name[20]; int gender; double height; double weight;
|
但是,使用 struct 关键词,可以聚合这几种数据类型成为一个新的数据类型。
1 2 3 4 5
| struct{ char name[20]; int gender; double height; double weight;}
|
上面这一串结构类型虽然很长,但是,就相当于 int 类型一样。 如同在 int 后填变量名可以声明一个整型变量。 在结构类型后面填写变量名可以声明一个结构变量。
1 2 3 4 5 6 7
| int n; struct { char name[20]; int gender; double height; double weight; }timmy;
|
timmy 是由该结构声明的变量,它由 4 个成员组成。
使用成员运算符 . 加上 字段名 可以访问到结构的各个成员。
timmy.name;
timmy.gender;
timmy.height;
timmy.weight;
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| struct { char name[20]; int gender; double height; double weight; }timmy;
strcpy(timmy.name, "Timmy"); timmy.gender = 1; timmy.height = 170.00; timmy.weight = 60;
printf("%s\n", timmy.name); printf("%d\n", timmy.gender); printf("%.2f\n", timmy.height); printf("%.2f\n", timmy.weight);
|
结构别名
现在,我们想定义多个人员信息结构变量。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct { char name[20]; int gender; double height; double weight; }timmy;
struct { char name[20]; int gender; double height; double weight; }david;
struct { char name[20]; int gender; double height; double weight; }jane;
|
由于这几个结构变量的内部成员都是一致的,能不能只声明一次结构类型,后续继续使用呢? 我们可以给结构类型声明取一个别名。
1 2 3 4 5 6 7 8
| struct person{ char name[20]; int gender; double height; double weight; }timmy; struct person david; struct person jane;
|
在第一次声明结构变量时,在 struct
与{
之间可以填写一个结构别名。
若以后再次需要使用这种结 构,仅需要使用 struct 加 别名 即可声明这种结构的变量。
事实上,我们可以将结构类型声明提取到最开头。让所有的结构变量均由别名来声明。相当于我们先造 了一个模板,然后,用这个模板生成各个变量。
1 2 3 4 5 6 7 8 9
| struct person{ char name[20]; int gender; double height; double weight; }; struct person timmy; struct person david; struct person jane;
|
请注意,如果结构类型声明在一个函数中,那么别名只能在函数内部使用。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| void func1() { struct person{ char name[20]; int gender; double height; double weight; }; struct person timmy; } void func2() {
struct person david; }
|
函数 func1 声明了一个结构类型,它的别名为 person 。并且使用别名声明了一个结构变量 timmy 。
函数 func2 中,使用别名 person ,声明另一个结构变量,但是别名 person 无法在函数 func2 中使用, 因此将编译报错。
如果需要在多个函数中使用结构别名,那么可以把它放到函数外面。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| struct person{ char name[20]; int gender; double height; double weight; }; void func1() { struct person timmy; } void func2() { struct person david; }
|
初始化结构
1 2 3 4 5 6 7 8 9 10 11
| struct person{ char name[20]; int gender; double height; double weight; }; struct person timmy = {"timmy", 1, 170.00, 60.00}; printf("%s\n", timmy.name); printf("%d\n", timmy.gender); printf("%.2f\n", timmy.height); printf("%.2f\n", timmy.weight);
|
结构的初始化列表的写法需要注意如下 4 点:
- 初始化列表由花括号包括。
- 花括号内为结构成员需要被初始化的值。
- **初始化值按照结构成员声明时的顺序依次排列 **
- 每个初始化值之间由逗号分隔。
对于第三点, person 结构成员声明的顺序依次为 name 、 gender 、 height 、 weight 。 对应的初始化列表中的初始化值顺序为”timmy”、 1、170.00、 60.00。 需要严格地对应顺序。
结构数组
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19
| struct person{ char name[20]; int gender; double height; double weight; }; struct person people[3] = { {"timmy", 1, 170.00, 60.00}, {"david", 1, 175.00, 65.00}, {"jane", 2, 165.00, 55.00} }; for(int i = 0; i < 3; i ++) { struct person per = people[i]; printf("%s ", per.name); printf("%d ", per.gender); printf("%.2f ", per.height); printf("%.2f\n", per.weight); }
|
嵌套结构
一个结构可以作为另一个结构的成员。
例如,我们声明一个结构,用于存储通讯方式。通讯方式由电话号码,邮箱组成。
1 2 3 4
| struct contact { char phone[20]; char email[20]; };
|
现在,我们需要记录每个人员的通讯方式。可以把这个结构作为人员结构的成员。
1 2 3 4 5 6 7
| struct person{ char name[20]; int gender; double height; double weight; struct contact c; };
|
初始化:
1 2 3
| struct person timmy = { "timmy", 1, 170.00, 60.00, {"130123456678", "timmy@xxx.com"} };
|
使用 . 加 字段名 可以访问到通讯方式结构。但是,你肯定还想访问其内部的成员,再次使 用 . 加 字段名 即可。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20
| struct contact { char phone[20]; char email[20]; }; struct person{ char name[20]; int gender; double height; double weight; struct contact c; }; struct person timmy = { "timmy", 1, 170.00, 60.00, {"130123456678", "timmy@xxx.com"} }; printf("%s ", timmy.name); printf("%d ", timmy.gender); printf("%.2f ", timmy.height); printf("%.2f\n", timmy.weight); printf("%s\n", timmy.c.phone); printf("%s\n", timmy.c.email);
|
指向结构的指针
1 2 3 4 5 6 7 8
| struct person{ char name[20]; int gender; double height; double weight; }; struct person timmy = {"timmy", 1, 170.00, 60.00}; struct person *pTimmy = &timmy;
|
由于取地址 & 与取值 * 它们具有可逆关系,我们可以把指针先转为结构再使用。
1 2 3 4
| printf("%s\n", (*pTimmy).name); printf("%d\n", (*pTimmy).gender); printf("%.2f\n", (*pTimmy).height); printf("%.2f\n", (*pTimmy).weight);
|
由于成员运算符 . 的优先级高于取值 _ 。为了让取值 _ 先运算符,必须使用括号 *pTimmy 包括。
另外,C 语言中提供了更加方便的写法,成员间接运算符 ->
。
(*pTimmy).name
等价于 pTimmy->name
。
结构在函数中传递
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| struct person{ char name[20]; int gender; double height; double weight; }; void change(struct person per) { strcpy(per.name, "david"); per.gender = 1; per.height = 175.00; per.weight = 65.00; } int main() { struct person timmy = {"timmy", 1, 170.00, 60.00}; change(timmy); printf("%s\n", timmy.name); printf("%d\n", timmy.gender); printf("%.2f\n", timmy.height); printf("%.2f\n", timmy.weight); return 0; }
|
现在,我们将结构当作参数传入函数。在函数内部修改传入的参数。 很显然,由于实参 timmy 与实参 per 是相互独立的。修改函数 change 内的 per 无法改动实参 timmy 。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| struct person{ char name[20]; int gender; double height; double weight; }; void change(struct person *per) { strcpy(per->name, "david"); per->gender = 1; per->height = 175.00; per->weight = 65.00; } int main() { struct person timmy = {"timmy", 1, 170.00, 60.00}; change(&timmy); printf("%s\n", timmy.name); printf("%d\n", timmy.gender); printf("%.2f\n", timmy.height); printf("%.2f\n", timmy.weight); return 0; }
|
但是,如果将 change 函数的参数改为指向结构的指针,情况就大不相同了。在函数 change 内部可以通 过指针,找到结构变量 timmy 。并且,对其进行修改
最后,将一个结构从函数返回也是可以的。从函数返回了 david 的数据,并且在将其赋值给了 timmy。
1 2 3 4 5 6 7 8 9 10 11 12
| struct person change() { struct person per; strcpy(per.name, "david"); per.gender = 1; per.height = 175.00; per.weight = 65.00; return per; }
struct person timmy = {"timmy", 1, 170.00, 60.00}; timmy = change();
|
类
类中的变量和函数被统一称为类的成员变量。
private
后面的内容是私有成员变量,在类的外部不能访问;public
后面的内容是公有成员变量,在类的外部可以访问。
类的使用:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51
| #include <iostream>
using namespace std;
const int N = 1000010;
class Person { private: int age, height; double money; string books[100];
public: string name;
void say() { cout << "I'm " << name << endl; }
int set_age(int a) { age = a; }
int get_age() { return age; }
void add_money(double x) { money += x; } } person_a, person_b, persons[100];
int main() { Person c;
c.name = "yxc"; c.age = 18; c.set_age(18); c.add_money(100);
c.say(); cout << c.get_age() << endl;
return 0; }
|
生成变量特殊方法:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| class player { int x, y; int speed; void move(int a, int b){ x += a * speed; y += b * speed; } }c, player[1000];
int main() { player players[1000]; return 0; }
|
类默认私有
1 2 3 4 5 6 7 8
| class player { int x, y; int speed; void move(int a, int b){ x += a * speed; y += b * speed; } };
|
访问控制(private、public)
1 2 3 4 5 6 7 8 9 10
| class player { public: int x, y; int speed; private: void move(int a, int b){ x += a * speed; y += b * speed; } };
|
C++类与结构体对比
区别:
作用上:class 默认 private,struct 默认 public。
使用上:引入 struct 是为了让 C++向后兼容 C。
推荐选用:
若只包含一些变量结构或 POD(plain old data)时,选用 struct。例
如数学中的向量类。
1 2 3 4 5 6 7
| struct Vec2{ float x, y; void Add(const Vec2& other){ x += other.x; y += other.y; } };
|
若要实现很多功能的类,则选用 class
若是只有数据,函数较少的,用 struct