记录一下c++的学习过程,笔记如有错误,欢迎指出!!!
文章目录
cpp简介
c++语言在c语言的基础上添加了面向对象编程和泛型编程的支持。c++继承了c语言高效,简洁,快速和可移植的传统。
c++融合了3种不同的编程方式:
- c语言代表的过程性语言.
- c++在c语言基础上添加的类代表的面向对象语言. (面向对象三个特性 封装, 继承, 多态
- c++模板支持的泛型编程。
c的第一个标准c89, c++的第一个标准c++98
基础知识
namespace 命名空间
用途
-
用于解决函数冲突, 不同文件中具有相同函数时,可以用namespace 包裹,以防产生歧义
//gam1.cpp #include "game1.h" void KingGlory::goAtk() { cout << "王者荣耀攻击实现" << endl; } //gam1.h #include <iostream> using namespace std; namespace KingGlory { void goAtk(); } //game2.cpp #include "game2.h" void LOL::goAtk() { cout << "LOL攻击实现" << endl; } // game2.h #include <iostream> using namespace std; namespace LOL { void goAtk(); }
-
命名空间下可以放, 变量, 函数, 结构体, 类…
namespace A { int m_A; void func(); struct Person {}; class Animal {}; }
-
命名空间必须声名在全局作用域下
void test02() { //namespace B{}; 不可以命名到局部作用域 }
-
命名空间可以嵌套命名空间
namespace B { int m_A = 10; namespace C { int m_A = 20; } }
-
命名空间是开放的,可以随时给命名空间添加新成员
namespace B { int m_A = 10; namespace C { int m_A = 20; } } namespace B { int m_B = 100; }
-
命名空间可以是匿名的,此时相当于加了 static 关键字
namespace { int m_C = 1000; int m_D = 2000; //当写的命名空间的匿名的,相当于写了 static int m_C = 1000; static int m_D = 2000; }
-
命名空间可以起别名
namespace veryLongName { int m_E = 10000; void func() { cout << "aaa" << endl; } } namespace veryShortName = veryLongName;
using的使用
using声名
namespace KingGlory
{
int sunwukongId = 1;
}
namespace LOL
{
int sunwukongId = 3;
}
void test01()
{
int sunwukongId = 2;
//1、using声明
//using KingGlory::sunwukongId ;
//当using声明与 就近原则同时出现,出错,尽量避免
cout << sunwukongId << endl;
}
using 编译指令
void test02()
{
//int sunwukongId = 2;
//2、using编译指令
using namespace KingGlory;
using namespace LOL;
//当using编译指令有多个,需要加作用域 区分
cout << KingGlory::sunwukongId << endl;
cout << LOL::sunwukongId << endl;
}
void test02()
{
int sunwukongId = 2;
using namespace KingGlory;
//当using编译指令 与 就近原则同时出现,优先使用就近
cout << sunwukongId << endl; //结果为2
}
C++相比C语言的增强
cpp中
.cpp
//1、全局变量检测增强 C++检测出重定义, 会报错
int a;
//int a = 10;
//2、函数检测增强 返回值检测、形参类型检测、函数调用参数个数
int getRectS(int w,int h)
{
return w *h;
}
void test01()
{
printf("%d\n", getRectS(10, 10));
}
//3、类型转换检测增强
void test02()
{
char * p = (char *)malloc(64);
}
//4、struct增强 C++可以放函数,创建结构体变量,可以简化关键字 struct
struct Person
{
int age;
void func()
{
age++;
}
};
void test03()
{
Person p;
p.age = 17;
p.func();
cout << "p的age = " << p.age << endl;
}
//5、bool类型扩展 C语言下 没有这个类型 C++有bool类型
bool flag = true; // bool类型 代表 真和假 true ---- 真(1) false ---- 假(0)
void test04()
{
cout << sizeof(bool) << endl; //结果是1个字节
//flag = false;
//flag = 100; //将非0的数都转为1
cout << flag << endl;
}
//6、三目运算符增强
void test05()
{
//?:
int a = 10;
int b = 20;
printf("ret = %d\n", a > b ? a : b);
(a < b ? a : b )= 100; // C++下返回的是变量 b = 100
printf("a = %d\n", a);
printf("b = %d\n", b);
}
//7、const增强
//全局const 和C语言结论一致
const int m_A = 100;
void test06()
{
//m_A = 200;
//int * p = (int *)&m_A;
//*p = 200;
//局部const
const int m_B = 100;
//m_B = 200;
int * p = (int *)&m_B; //&m_B原本为const int *类型,要想修改,需进行类型转换,虽然转换后修改,值仍不会变化,但不会报错
*p = 200;
cout << "m_B = " << m_B << endl; //结果没变
int arr[m_B]; //C++下const修饰的变量 称为常量 ,可以初始化数组
}
c中
//1、全局变量检测增强
int a;
int a = 10;
//2、函数检测增强 返回值没有检测 形参类型没有检测 函数调用参数个数没有检测
getRectS( w , h)
{
return w *h;
}
void test01()
{
printf("%d\n", getRectS(10, 10, 10));
}
//3、类型转换检测增强
void test02()
{
char * p = malloc(64);
}
//4、struct增强
struct Person
{
int age;
//void func(); C语言下 结构体不可以有函数
};
void test03()
{
struct Person p; //创建结构体变量时候,必须加关键字struct
p.age = 100;
}
//5、bool类型扩展 C语言下 没有这个类型
//bool a;
//6、三目运算符增强
void test04()
{
//?:
int a = 10;
int b = 20;
printf("ret = %d\n", a > b ? a : b);
*(a > b ? &a : &b) = 100; //C语言下 返回的是值 20 = 100
printf("a = %d\n", a);
printf("b = %d\n", b);
}
//7、const增强
//全局const
const int m_A = 100; // 受到常量区保护,运行修改失败
void test05()
{
//m_A = 200;
//int * p = &m_A;
//*p = 200;
//局部const
const int m_B = 100; //分配到栈上
//m_B = 200;
int * p = &m_B;
*p = 200;
printf("%d\n", m_B);
//int arr[m_B]; 在C语言下 const是伪常量,不可以初始化数组
}
cpp中的const
Cpp中,const修饰的全局变量不可修改,也不可使用指针修改
使用指针修改const局部变量(假设此变量定义时,直接用字面值给其赋值,只有这种情况下)时,虽然不会报错, 局部变量的值不会变化,原因如下,const常量存在符号表中
外部链接和内部链接
c语言中 const修饰的全局变量,默认是外部链接属性,在其他文件中定义,在另一文件中用extern声名,即可使用
cpp中,const修饰的全局变量,默认是内部链接属性,在其他文件中定义,在另一文件中即使用extern声名,也不能使用,要想修改为外部链接属性需要在定义时用extern修饰
const 分配内存情况
//1、对const变量 取地址 ,会分配临时内存,此时不能通过指针修改const 变量的值,只能改变临时内存中的值,因为变量放在了符号表中
void test01()
{
const int a = 10;
int * p = (int *)&a;
}
//2、使用普通变量 初始化 const变量, 此时可以通过指针修改const 变量的值,注意此时用的指针是普通指针
void test02()
{
int a = 10;
const int b = a;
int *p = (int *)&b;
*p = 1000;
cout << "b = " << b << endl;
}
//3、对于自定义数据类型, 此时可以通过指针修改const 变量的值,注意此时用的指针是普通指针
struct Person
{
string m_Name;
int m_Age;
};
void test03()
{
const Person p;
//p.m_Age = 10;
Person * pp = (Person *)&p;
(*pp).m_Name = "Tom";
pp->m_Age = 10;
cout << "姓名: " << p.m_Name << " 年龄: " << p.m_Age << endl;
}
const 和 #define区别总结:
- const有类型,可进行编译器类型安全检查。#define无类型,不可进行类型检查.
- const有作用域,而#define不重视作用域,默认定义处到文件结尾.如果定义在指定作用域下有效的常量,那么#define就不能用。
因此尽量用const 代替define
引用
引用的目的,起别名
引用注意事项:
- 引用定义时,必须初始化
- 引用一旦初始化后,就不可以引向其他变量,均看成赋值操作
- 引用类型必须域原名类型一致
对数组的引用
// 1.直接引用
int arr[10];
int (&b)[10] = arr;
//2.先建立类型别名,再引用
int arr[10];
typedef int (parr)[10];
prrr &b = arr;
参数的传递方式
- 值传递,不能修改实参
- 地址传递,可以修改实参
- 引用传递,通过别名修改实参
引用注意事项
-
引用必须引用一块合法内存空间 int &a = 10,错
-
不要返回局部变量的引用,因为函数执行完后,局部变量被释放,要想返回可以用malloc, 或者 static修饰
-
当函数返回值是引用,那么函数的调用可以作为左值,如下段代码所示
-
int& func() { int a = 10; return a; } //引用注意事项 void test02() { //1、引用必须引一块合法内存空间 //int &a = 10; //2、不要返回局部变量的引用 int &ref = func(); cout << "ref = " << ref << endl; cout << "ref = " << ref << endl; } int& func2() { static int a = 10; return a; } void test03() { int &ref = func2(); cout << "ref = " << ref << endl; cout << "ref = " << ref << endl; cout << "ref = " << ref << endl; cout << "ref = " << ref << endl; //当函数返回值是引用,那么函数的调用可以作为左值 func2() = 1000; cout << "ref = " << ref << endl; }
引用的本质是指针常量
指针的引用可以简化指针操作,在函数中不需要传入高级指针
type * &p :指针的引用,由内向外解读
struct Person
{
int age;
};
void allocateSpace(Person ** p)
{
//p指向指针的指针 *p 指针 指向的是person 本体 **p person本体
*p = (Person *)malloc(sizeof(Person));
(*p)->age = 10;
}
void test01()
{
Person * p = NULL;
allocateSpace(&p);
cout << "p.age = " << p->age << endl;
}
void allocateSpace2(Person* &pp) // Person * &pp = p;
{
pp = (Person *)malloc(sizeof(Person));
pp->age = 20;
}
void test02()
{
Person *p = NULL;
allocateSpace2(p);
cout << "p.age = " << p->age << endl;
}
常量的引用
- 正常情况下 int &a = 10,是错误的, 因为引用必须指向一块内存,
而使用const int &a = 10 不会报错,加了const 后相当于, 写成 int temp = 10; const int &ref = temp - 此外,常量的引用通常用作函数形参,防止误操作
内联函数
宏缺陷
//宏缺陷1 : 必须要加括号保证运算完整
#define MYADD(x,y) ((x) +(y))
void test01()
{
int a = 10;
int b = 20;
int ret = MYADD(a, b) * 20;
cout << ret << endl;
}
//宏缺陷2: 即使加了括号,有些运算依然与预期不符
#define MYCOMPARE(a,b) (((a) < (b)) ? (a) : (b))
在c++中,预定义宏的概念是用内联函数来实现的,而内联函数本身也是一个真正的函数。内联函数具有普通函数的所有行为。唯一不同之处在于内联函数会在适当的地方像预定义宏一样展开,所以不需要函数调用的开销。因此应该不使用宏,使用内联函数。
优势 :解决宏缺陷,本身是一个函数,以空间换时间,会在适当的时候展开(不用入栈出栈)
使用方法:
- 内联函数用inline设置,若要使用, 函数的声名和实现必须同时加关键字inline
- 类内成员函数,在函数前都隐式加了inline
注意 c++内联编译会有一些限制,以下情况编译器可能考虑不会将函数进行内联编译:
- 不能存在任何形式的循环语句
- 不能存在过多的条件判断语句
- 函数体不能过于庞大
- 不能对函数进行取址操作
内联仅仅只是给编译器一个建议,编译器不一定会接受这种建议,如果你没有将函数声明为内联函数,那么编译器也可能将此函数做内联编译。一个好的编译器将会内联小的、简单的函数。
默认参数和占位参数
默认参数
//默认参数 语法 形参 类型 变量 = 默认值
//注意事项 ,如果有一个位置有了默认参数,那么从这个位置起,从左到右都必须有默认值
int func(int a, int b = 10 , int c = 10)
{
return a + b + c;
}
void test01()
{
cout << func(20 , 10) << endl;
}
//函数的声明和实现 只能有一个 提供默认参数,不可以同时加默认参数
void myFunc(int a = 10, int b = 10);
void myFunc(int a , int b){};
占位参数
//占位参数 只写一个类型进行占位,调用时候必须要传入占位值,若占位值有默认值,则不需要传入
//占位参数 用途? 目前没用
void func2(int a , int = 1)
{
}
void test02()
{
func2(10);
}
int main(){
test01();
函数重载
函数重载条件:
- 在同一个作用域
- 函数名称相同
- 参数个数、类型、顺序不同
- 返回值不可以作为函数重载的条件,因为存在二义性
函数重载中 引用两个版本
//函数重载中 引用两个版本
//void myFunc(int a)
//{
// cout << "myfunc(int a )调用" << endl;
//}
void myFunc(int &a) // int & a = 10;
{
cout << "myfunc(int &a )调用" << endl;
}
void myFunc(const int &a) // const int &a = 10;
{
cout << "myfunc( const int &a )调用" << endl;
}
void test02()
{
int a = 10;
//myFunc(a);//三个版本可以同时存在,但是需要避免二义性出现,
// int &a 和const int &a 根据参数的参数选择最匹配的函数,假设没有void myFunc(int a),则
myFunc(10) //调用void myFunc(const int &a)
myFunc(a); //调用myFunc(int &a)
}
函数重载碰到默认参数,不会报错,使用时注意避免二义性出现
void func2(int a , int b = 10)
{
}
void func2(int a)
{
}
void test03()
{
//func2(10); //出现二义性
}
函数重载实现原理
编译器为了实现函数重载,也是默认为我们做了一些幕后的工作,编译器用不同的参数类型来修饰不同的函数名,比如void func(); 编译器可能会将函数名修饰成_func,当编译器碰到void func(int x),编译器可能将函数名修饰为_func_int,当编译器碰到void func(int x,char c),编译器可能会将函数名修饰为_func_int_char我这里使用”可能”这个字眼是因为编译器如何修饰重载的函数名称并没有一个统一的标准,所以不同的编译器可能会产生不同的内部名。
externC浅析
cpp中由于可以函数重载,因此会对函数进行修饰,在c语言中共不会进行修饰,因此,在cpp中调用 .c文件中的函数会在链接时找不到函数而出错
//告诉编译器 show函数用C语言方式 做链接, 在调用该函数的文件中声名,这样做需要对每一个使用到的函数进行声名,太麻烦
extern "C" void show();
//所以,可以在.c文件对应的.h文件中进行处理
#ifdef __cplusplus // 两个下划线 __ c plus plus
extern "C" {
#endif
#include <stdio.h>
void show();
#ifdef __cplusplus
}
#endif
类和对象
c语言会导致属性和行为分离
c++的封装
- c++将属性和行为作为一个整体,来表现生活中的事物
- 将属性和行为加以权限控制
struct 和class的区别
- class 默认权限 私有权限,而struct默认权限是公有权限
访问权限 public private protected
- public 公共权限 成员 类内 类外都可以访问
- private 私有权限 成员 类内 可以访问 类外 不可以访问,子类不可以访问父类的private权限内容
- protected 保护权限 成员 类内 可以访问 类外不可以访问 子类可以访问父类的protected权限内容
类内可以多次声名访问权限
尽量将成员属性设置为私有 好处:
- 自己可以控制读写权限
- 可以对设置内容 加有效性验证
此会对函数进行修饰,在c语言中共不会进行修饰,因此,在cpp中调用 .c文件中的函数会在链接时找不到函数而出错
//告诉编译器 show函数用C语言方式 做链接, 在调用该函数的文件中声名,这样做需要对每一个使用到的函数进行声名,太麻烦
extern "C" void show();
//所以,可以在.c文件对应的.h文件中进行处理
#ifdef __cplusplus // 两个下划线 __ c plus plus
extern "C" {
#endif
#include <stdio.h>
void show();
#ifdef __cplusplus
}
#endif
类和对象
c语言会导致属性和行为分离
c++的封装
- c++将属性和行为作为一个整体,来表现生活中的事物
- 将属性和行为加以权限控制
struct 和class的区别
- class 默认权限 私有权限,而struct默认权限是公有权限
访问权限 public private protected
- public 公共权限 成员 类内 类外都可以访问
- private 私有权限 成员 类内 可以访问 类外 不可以访问,子类不可以访问父类的private权限内容
- protected 保护权限 成员 类内 可以访问 类外不可以访问 子类可以访问父类的protected权限内容
类内可以多次声名访问权限
尽量将成员属性设置为私有 好处:
- 自己可以控制读写权限
- 可以对设置内容 加有效性验证