c++学习记录
第一章:c++简介
c++的历史:
C语言有很多优点,也有很多不足。如对类型匹配的检查不够严格,基本没有支持代码重用的机制,不支持面向对象等。这使得在用C语言开发大规模的软件时,维护和扩充都比较困难。1980年,贝尔实验室开始对C语言进行改进,为其加入面向对象的特性,这种新语言被称为“带类的c”, 但在后来,c++又引入了一些新的函数库和新的用法,如虚函数(virtual function)、操作符重载(operator overloading)、多重继承(multiple inheritance)、模板(template)、异常处理(exception)、RTTI(Runtime type information)、命名空间(namespace)逐渐纳入,逐步增强了c++的可用。
c++的概述:
- c++是在C语言的基础上的一个增强版本
- c++可以调用c的代码和库
- c++是面向对象编程,c语言是面向过程编程
- c++支持泛型编程
c++的标准:
c++98标准(ISO/IEC 14882:1998)
c++11标准(ISO/IEC 14882:2011)
第二章:认识类和对象
第一个helloworld程序
#include <iostream> //标准的输入输出流
using namespace std;//声明一个命名空间,cout对象在std作用下
int main()
{
//cout标准输出流
//endl换行
cout << "hello world" << endl;
return 0;
}
分析:
#include;预编译指令,引入头文件iostream
std为标准命名空间
cout << “hello world” <<endl;和printf功能相同,输出字符串“hello world”
endl表示换行。
在c++中,头文件不在以.h结尾。一些C语言中常用的头文件在c++中的名字去掉.h并在开头加上字符c列如:
#include
#include
#include
面向过程编程:
面向过程编程思想:分析出解决问题所需要的步骤,然后用函数把这些步骤一步一步实现
面向对象编程:
对象:世界上任何事物都可以看成一个对象(属性+行为)
面向过程是具体化的,流程化的,解决一个问题,你需要一步一步的分析,一步一步的实现。面向对象是模型化的,你只需抽象出一个类,这是一个封闭的盒子,在这里你拥有数据也拥有解决问题的方法。需要什么功能直接使用就可以了,不必去一步一步的实现,至于这个功能是如何实现的,管我们什么事?我们会用就可以了。
面向对象的三大特点:
封装:将属性和方法(函数)封装在一起抽象成一个类(结构体),并且对类中的成员加以权限控制。
继承:将一个类中属性和方法继承到另一个类中。
多态:一个接口多种形态(静态多态,动态多态)。
第三章 :c++对c语言的扩展
c++对c的扩展:
冒号作用域:
::运算符是一个作用域,如果::前面什么都没有加,代表是全局作用域。
#include <iostream>
using namespace std;
int a = 100;
void test01()
{
int a = 10;
cout << a << endl;//输出局部变量a
cout << ::a << endl;//输出全局变量a
}
int main()
{
test01();
return 0;
}
命名空间:
namespace:本质上作用域,可以更好的控制标识符的作用域。
命名空间可以存放 变量,函数,类,结构体…
命名空间的使用:
- 命名空间的定义必须在全局范围
- 命名空间可以重名,重名的命名空间相当于合并操作
- 命名空间可以嵌套命名空间
- 命名空间如果没有名字,那么这个命名空间内的所有成员都被编译器加上了static修饰符,只能被当前文件调用,属于内部链接属性
- 命名空间是可以取别名的 namespace newname = oldname;
#include <iostream>
using namespace std;
namespace A
{
int a = 10;
void fun()
{
cout << "hello world" << endl;
}
struct std
{};
class obj
{};
void foo(int arg)
{};
}
void A::foo(int arg)
{
cout << arg << endl;
}
namespace B
{
int a = 10;
int b = 20;
}
namespace B
{
int c = 30;
}
namespace C
{
int a = 100;
int b = 200;
namespace D
{
int a = 900;
}
}
void test01()
{
cout << A::a << endl;
cout << B::a << endl;
cout << B::b << endl;
A::fun();
cout << B::c << endl;
}
void test02()
{
cout << C::a << endl;
cout << C::D::a << endl;
}
void test03()
{
A::foo(222);
}
int main()
{
test01();
return 0;
}
using的声明和编译指令:
using的声明可以使得指定标识符可用.命名空间标识符如果和局部变量标识符同名,程序会报错。
using的编译指令使整个命名空间标识符可用,并且命名空间标识符如果和局部变量标识符同名,不会有冲突,优先使用局部变量。
#include <iostream>
using namespace std;
namespace nameA
{
int a = 10;
void foo()
{
cout << "hello using" << endl;
}
}
void test01()
{
//当using声明的标识符和其他标识符有作用域的冲突时,会产生二义性
//int a = 100;(报错)
using nameA::a;
using nameA::foo;
cout << a << endl;//cout << nameA::a << endl;
}
void test02()
{
//int a = 100;(不报错,并打印100)
using namespace nameA;
cout << a << endl;
foo();
}
int main()
{
test01();
return 0;
}
c与c++的其它区别:
全局变量的检测:
c++的编译器对于全局变量的声明和定义有严格的区分
//全局变量
int a;//定义
extern int a;//声明
如果写出以下形式,编译时不会通过
//全局变量
int a;
int a;
所有变量和函数都要有类型:
c++中的函数形参类型必须写,没有返回值不可以返回,实参的个数要做检测。
更严格的类型转换:
c++不同类型的变量不能直接赋值的,需要相应的强转。
struct类型增强:
在c++中使用结构体时,可以不写struct关键字。
struct stu
{
int a;
int b;
};
int main()
{
stu obj;//C语言中为 struct stu obj;
}
新增bool类型关键字:
在C语言中使用bool类型需要导入stdbool.h头文件,而c++可以直接使用bool类型变量。
三目运算符的增强:
在c语言中三目运算符返回的是表达式的值,是一个常量
而在c++中三目运算符返回的是一个变量。
int main()
{
int a = 10;
int b = 20;
(a < b ? a : b) = 100;//编译可以通过
}
c/c++的const:
const是一个关键字,用来限定一个变量不允许改变,他将一个对象转换成一个常量。const修饰的常量不能改变
C语言:
- C语言修饰的局部变量存在栈区,虽然不能通过变量赋值修改,但是可以通过指针修改
- const修饰的全局变量是保存在常量区,不能通过变量赋值和指针修改
- const修饰的全局变量,如果其他文件想使用,直接extern声明外部即可使用
#include <stdio.h>
const int b = 10;//const修饰的全局变量保存在常量区
void test01()
{
extern const int num;//声明num是外部可用
printf("num = %d\n",num);
}
void test02()
{
int *p = &b;
*p = 100;//错误的 不能修改常量区的值
printf("b = %d\n",b);
}
void test03()
{
const int a = 10;//C语言中的const修饰的变量保存在栈区
int *p = &a;
*p = 100;
printf("a = %d\n",a);
}
int main()
{
test03();
return 0;
}
c++:
- const修饰的局部变量赋值为常量时,局部变量保存在符号表中,不能修改,是一个常量
- const修饰的全局变量保存在常量区,不能修改
- const修饰的全局变量默认是内部链接属性,加上extern修饰变成外部链接属性
- const修饰的全局变量在常量区分配了内存
- 对const修饰的局部变量赋值为常量时,对其取地址,会在栈区分配临时的内存空间
- const修饰的静态成员变量保存在常量区,不可以修改,在内存中只有一份
#include <iostream>
using namespace std;
void test01()
{
const int a = 10;
int *p = (int *)&a;//对const修饰的局部变量区地址,编译器会产生一个临时的变量tmp来保存a的地址
*p = 100;
cout << a << endl;
}
int main()
{
test01();
return 0;
}
void teest01()
{
int b = 2;
const int a = b;
int *p = (int *)&a;
*p = 100;
cout << a << endl;//a的值被修改了
}
struct stu{
int a;
int b;
};
int main()
{
const stu obj = {1,2};
struct stu *p =(struct stu *)&obj;
p -> a = 3;
p -> b = 4;
cout << a << " " << b << endl;//修改ab的值
}
c和c++的异/同:
- c和c++中的const修饰的全局变量是保存在常量区,不能被修改
- c语言中const修饰的局部变量赋值为常量时,局部变量保存在栈区,可以被指针修改
- c++中的const修饰的局部变量赋值为常量时,局部变量保存在符号表中,不能修改
- C语言中const修饰的全局变量默认是外部链接属性
- c++中const修饰的全局变量默认是内部链接属性
引用:
引用的用法:原类型 &别名 = 旧名 (类型名 & 引用名 = 同类型的某变量名)
引用: 为对象起另一个名字,引用类型引用另一种类型。
引用能起到指针的部分作用,但是比指针安全。一个引用可以看作是某个变量的一个"别名"。对引用进行操作就像对原变量进行操作一样。
注意事项:
- 引用一旦初始化,不能更改引用的指向
- 引用定义时必须初始化
- 不能引用NULL
- 引用可以引用任意类型包括数组
- &在等号左边是引用,在右边是取地址
int a = 10;
int &b = a;
b = 100;
int c = 1;
b = c;//把c的值给b,不是给c取别名
int a = 1;
int &b = a;
b = 2;
cout << a << endl;//输出2
cout << b << endl;//输出2
a = 3;
cout << b << endl;//输出3
int a[5] = {1,2,3,4,5};
int &arr[5] = a;
函数中的引用:
- 不能返回局部变量的引用
- 引用可以作为函数的形参
#include <iostream>
using namespace std;
void swap(int *x,int *y)
{
tmp = *x;
*x = *y;
*y = tmp;
}
void swap_ref(int &x,int &y)//int &x = a,int &y = b
{
int tmp = x;
x = y;
y = tmp;
}
void get_mem(int **q)//开辟空间
{
*q = (int *)malloc(5*sizeof(int));
}
void get_mem_ref(int *&q)//int * &q = p
{
q = (int *)malloc(5*sizeof(int));
}
void test01()
{
int a = 10;
int b = 20;
swap(&a,&b);
cout << a << " " << b << endl;
}
void test01_ref()
{
int a = 10;
int b = 20;
swap_ref(a,b);
cout << a << " " << b << endl;
}
void test02()
{
int *p = NULL;
get_mem(&p);
get_mem_ref(p);
}
int main()
{
test01_ref();
}
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <string.h>
#include <string>
using namespace std;
int n = 4;
int & test01()
{
return n;//返回对n的引用
}
int main()
{
test01() = 40;//返回值是引用的函数调用表达式,可以作为左值
cout << n << endl;//输出40
int& r = test01();
cout << r << endl;//输出40
}
引用的本质在c++内部实现是一个指针常量
type &b = a;在编译器中是:type *const b = &a;
int a = 10;
int &b = a;//编译器优化 int * const b = &a;
b = 1000//*b = 1000
//指针常量不能改变指针变量的指向 b = 0x1000
void fun(int *&q)//int *&q = p ==>int * const q = &p
四,指针的引用
type &q = p
type为指针p的类型
void fun(int *&q)//int *&q = p
{
}
int main()
{
int *p = NULL;
fun(p);
}
五,常量的引用
const type &p = q;
常量的引用代表不能通过引用去修改引用标识的那块空间
内联函数:
1.为什么要有内联函数:
第一个在c中也会出现,宏看起来像一个函数调用,但是会有隐藏一些难以发现的错误。
第二个问题c++独有的,预处理器不允许访问类的成员,也就是说预处理器宏不能类的成员函数。
内联函数就是继承了宏函数的高效,并且不会出错,还可以当成类的成员函数用。
2.宏函数和内联函数的区别:
- 宏函数替换发生在预处理阶段
- 内联函数替换发生在编译阶段
- 宏函数容易出错,内联函数不会出错
- 内联函数和宏函数一样省去了调用函数的开销
#include <iostream>
using namespace std;
#define MYADD(a,b) a+b
inline int myadd(int a, int b)
{
return a + b;
}
void test01()
{
int a = 1;
int b = 2;
int c = MYADD(a,b);//a+b,替换发生在预处理阶段
//int c = MYADD(a,b)*5;//a+b*5,替换发生在编译阶段
int d = myadd(a,b)*5;//(a+b)*5,替换发生在编译阶段,也和宏函数一样不会有调用函数的开销
cout << c << endl;
}
int main()
{
test01();
}
3.内联函数的概念:
在c++中,预定义宏的概念是用内联函数实现的,而内联函数本身也是一个真正的函数,内联函数具有普通函数的所有行为,唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,多以不需要函数调用的开销,因此不应该使用宏,而是内联函数。
在普通函数前面加上inline关键字使之成为内联函数,但是必须注意函数体和声明结合在一起,否则编译器将他作为普通函数处理
inline int fun(int a)(return ++);
内联函数的确占用空间,但是内联函数相对于普通函数的优势只是省去了函数调用时的压栈,跳转,返回的开销,我们可以理解内联函数是以空间换时间。
4.类的成员函数默认编译器会将它做成内联函数
任何在类内部定义的函数自动成为内联函数。
class Person{
public:
Person() {cout << "构造函数" << endl;}
void PrintPerson() {cout << "输出Person" << endl;}
}
5.内联函数和编译器:
不能存在任何形式的循环语句
不能存在过多的的条件判断
函数体不能过大
不能对函数进行去地址操作
内联函数仅仅是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联函数,一个好的编译器将会内联小的,简单的函数。
函数的默认参数:
c++中的函数,形参可以设置默认参数,设置时需要注意以下几点:
- 设置默认参数时ÿ