C++笔记归纳1:C++入门基础
namespace + 命名空间的名字,{}内为命名空间的成员命名空间中可以定义变量、函数、类型等printf函数访问的是全局域中rand函数,而非命名空间域中的rand变量2.3.namespace的本质定义出一个域,这个域全局域各自独立,不同的域可以定义同名变量域作用限定符:::在全局域中创建一个变量a,在main函数中也创建一个变量a如果在main函数中打印,则会优先打印main函数中的变量
C++入门基础
目录
一、第一个C++程序
#include <iostream>
using namespace std;
int main()
{
cout << "hello world\n" << endl;
return 0;
}
二、命名空间
2.1.C语言命名冲突问题
C语言的变量、函数的名称都存在于全局作用域中,可能会导致冲突
C++引入namespace关键字,对标识符的名称进行本地化,避免问题

2.2.namespace的定义
namespace + 命名空间的名字,{}内为命名空间的成员
命名空间中可以定义变量、函数、类型等


printf函数访问的是全局域中rand函数,而非命名空间域中的rand变量


2.3.namespace的本质
定义出一个域,这个域全局域各自独立,不同的域可以定义同名变量
域作用限定符:::


在全局域中创建一个变量a,在main函数中也创建一个变量a
如果在main函数中打印,则会优先打印main函数中的变量a
如果想打印全局域中的变量a,就需要在a前加域作用限定符
要使用域中的变量,需要在变量前加域名称与域作用限定符
示例:


2.4.C++中的作用域
函数局部域 全局域
命名空间域 类域
域隔离解决了名字冲突的问题
局部域和全局域不仅会影响编译查找逻辑,还会影响变量生命周期
域影响的是编译时语法查找一个变量、函数、类型声明或定义的逻辑


2.5.namespace的嵌套定义
#include <iostream>
namespace bit
{
// 鹏哥
namespace pg
{
int rand = 1;
int Add(int left, int right)
{
return left + right;
}
}
// 杭哥
namespace hg
{
int rand = 2;
int Add(int left, int right)
{
return (left + right) * 10;
}
}
}
int main()
{
printf("%d\n", bit::pg::rand);
printf("%d\n", bit::hg::rand);
printf("%d\n",bit::pg::Add(1,2));
printf("%d\n",bit::hg::Add(1,2));
return 0;
}

2.6.namespace的跨文件扩展
namespace可以在多个文件中重复定义同一命名空间域
示例:namespace定义栈的头文件与源文件
Stack.h:
#pragma once
#include<stdio.h>
#include<stdlib.h>
#include<stdbool.h>
#include<assert.h>
namespace bit
{
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST* ps, int n);
void STDestroy(ST* ps);
void STPush(ST* ps, STDataType x);
void STPop(ST* ps);
STDataType STTop(ST* ps);
int STSize(ST* ps);
bool STEmpty(ST* ps);
}
Stack.cpp:
#define _CRT_SECURE_NO_WARNINGS 1
#include"Stack.h"
namespace bit
{
void STInit(ST * ps, int n)
{
assert(ps);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
// 栈顶
void STPush(ST * ps, STDataType x)
{
assert(ps);
// 满了, 扩容
if (ps->top == ps->capacity)
{
printf("扩容\n");
int newcapacity = ps->capacity == 0 ? 4 : ps->capacity* 2;
STDataType * tmp = (STDataType*)realloc(ps->a,
newcapacity * sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
ps->a = tmp;
ps->capacity = newcapacity;
}
ps->a[ps->top] = x;
ps->top++;
}
//...
}
test.cpp:
#include"Stack.h"
// 全局定义了⼀份单独的Stack
typedef struct Stack
{
int a[10];
int top;
}ST;
void STInit(ST* ps) {}
void STPush(ST* ps, int x) {}
int main()
{
// 调⽤全局的
ST st1;
STInit(&st1);
STPush(&st1, 1);
STPush(&st1, 2);
printf("%zd\n", sizeof(st1));
// 调⽤bit namespace的
bit::ST st2;
printf("%zd\n", sizeof(st2));
bit::STInit(&st2, 4);
bit::STPush(&st2, 1);
bit::STPush(&st2, 2);
return 0;
}

2.7.std标准命名空间
C++标准库都放在一个叫std(standard)的命名空间中
2.8.namespace的使用
指定命名空间访问
#include <stdio.h>
int a = 0;
int main()
{
int a = 1;
printf("%d\n", a);
printf("%d\n", ::a);
return 0;
}

using将命名空间中所有成员展开
#include <stdio.h>
namespace bit
{
int a = 0;
int b = 1;
}
using namespace bit;
int main()
{
printf("%d\n", a);
printf("%d\n", b);
return 0;
}

using将命名空间中某个成员展开
#include <stdio.h>
namespace bit
{
int a = 0;
int b = 1;
}
using bit::a;
int main()
{
printf("%d\n", a);
printf("%d\n", a);
printf("%d\n", a);
printf("%d\n", a);
printf("%d\n", a);
printf("%d\n", a);
printf("%d\n", bit::b);
return 0;
}

三、C++的输入输出
3.1.C语言输入输出局限
C语言的输入输出需要手动指定变量类型
C++的输入输出可以自动识别变量类型(函数重载)
更好的支持自定义类型对象的输入输出
3.2.输入输出流
<iostream>(Input Output Stream):标准输入输出流库,定义了标准的输入、输出对象
std::cin:istream类的对象,主要面向窄字符的标准输入流
std::cout:ostream类的对象,主要面向窄字符的标准输出流
std::endl:函数、流插入输出时,相当于插入
<<:流插入运算符
>>:流提取运算符
注:窄字符(narrow characters (of type char))
内存中有整型、浮点型的存储形式
但转入其他文件后就只有字符形式
示例:
#include <iostream>
int main()
{
int i = 1234;
std::cout << i << std::endl;
return 0;
}
#include <iostream>
using namespace std;
int main()
{
int i = 1234;
cout << i << endl;
return 0;
}
将内存中变量i的二进制数据以字符形式插入到标准输出流(控制台)中

3.3.流插入(cout)
#include <iostream>
using namespace std;
int main()
{
int a = 0;
double b = 0.1;
char c = 'x';
cout << a << " " << b << " " << c << " ";
cout << a << " " << b << " " << c << endl;
cout << a << " " << b << " " << c << '\n';
cout << a << " " << b << " " << c << " ";
return 0;
}
注:
流插入运算符<<是一个二元操作符,只能有两个操作数
endl函数具有换行的作用

3.4.流提取(cin)
#include <iostream>
using namespace std;
int main()
{
int a;
double b;
char c;
cin >> a;
cin >> b >> c;
cout << a << endl;
cout << b << " " << c << endl;
return 0;
}
注:
流提取运算符>>不需要输入变量的地址

3.5.IO效率提高
在io需求比较高的地方,比如部分大量输入的竞赛题中
加上以下3行代码,可以提高C++的IO效率
int main()
{
ios_base::sync_with_stdio(false);
cin.tie(nullptr);
cout.tie(nullptr);
return 0;
}
四、缺省参数(默认参数)
缺省参数:声明或定义函数时为函数参数指定一个缺省值
没有传参时:使用参数的默认值(缺省值)
有参数传送:使用指定的实参
#include <iostream>
using namespace std;
void Func(int a = 0)
{
cout << a << endl;
}
int main()
{
Func();
Func(10);
return 0;
}

4.1.全缺省
全部形参给缺省值
#include <iostream>
using namespace std;
// 全缺省
void Func1(int a = 10, int b = 20, int c = 30)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func1();
Func1(1);
Func1(1, 2);
Func1(1, 2, 3);
return 0;
}

注:错误传法
Func1( ,2, );
4.2.半缺省
部分形参给缺省值
必须从右往左连续缺省,不能间隔、跳跃
#include <iostream>
using namespace std;
//半缺省
void Func2(int a, int b = 10, int c = 20)
{
cout << "a = " << a << endl;
cout << "b = " << b << endl;
cout << "c = " << c << endl << endl;
}
int main()
{
Func2(100);
Func2(100, 200);
Func2(100, 200, 300);
return 0;
}

4.3.缺省参数的作用
// Stack.h
#include <iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType * a;
int top;
int capacity;
}ST;
void STInit(ST * ps, int n = 4);
// Stack.cpp
#include"Stack.h"
// 缺省参数不能声明和定义同时给
void STInit(ST * ps, int n)
{
assert(ps && n > 0);
ps->a = (STDataType*)malloc(n * sizeof(STDataType));
ps->top = 0;
ps->capacity = n;
}
// test.cpp
#include"Stack.h"
int main()
{
ST s1;
STInit(&s1);
// 确定知道要插⼊1000个数据,初始化时⼀把开好,避免扩容
ST s2;
STInit(&s2, 1000);
return 0;
}
注:
函数声明和定义分离时,缺省参数不能在函数声明和定义中同时出现
规定必须在函数声明时给缺省值
五、函数重载
C++支持在同一作用域中出现同名函数
但是要求同名函数的形参不同
5.1.参数的类型不同
#include <iostream>
using namespace std;
// 1、参数类型不同
int Add(int left, int right)
{
cout << "int Add(int left, int right)" << endl;
return left + right;
}
double Add(double left, double right)
{
cout << "double Add(double left, double right)" << endl;
return left + right;
}
int main()
{
Add(1, 2);
Add(1.1, 1.1);
return 0;
}

5.2.参数的个数不同
#include <iostream>
using namespace std;
// 2、参数个数不同
void f()
{
cout << "f()" << endl;
}
void f(int a)
{
cout << "f(int a)" << endl;
}
int main()
{
f();
f(10);
return 0;
}

5.3.参数类型顺序不同
#include <iostream>
using namespace std;
// 3、参数类型顺序不同
void f(int a, char b)
{
cout << "f(int a,char b)" << endl;
}
void f(char b, int a)
{
cout << "f(char b, int a)" << endl;
}
int main()
{
f(10, 'a');
f('a', 10);
return 0;
}

5.4.特殊情况
返回值无法作为函数重载条件,因为调用时也无法区分
//返回值不同不能作为重载条件,因为调⽤时也⽆法区分
void fxx()
{
}
int fxx()
{
return 0;
}
下面两个函数构成重载
f()中无实参时,调用时会报错,存在歧义,编译器不知道调用谁
f()中有实参时,就明确要调用第二个f1函数
void f1()
{
cout << "f()" << endl;
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
解决方案:将两个函数放在不同的域中(此时不为函数重载)
#include <iostream>
using namespace std;
namespace bit1
{
void f1()
{
cout << "f()" << endl;
}
}
void f1(int a = 10)
{
cout << "f(int a)" << endl;
}
int main()
{
bit1::f1();
f1();
return 0;
}

六、引用
6.1.引用的概念
引用:给已存在变量取一个别名
#include<iostream>
using namespace std;
int main()
{
int a = 0;
// 引⽤:b和c是a的别名
int& b = a;
int& c = a;
// 也可以给别名b取别名,d相当于还是a的别名
int& d = b;
++d;
// 这⾥取地址我们看到是⼀样的
cout << &a << endl;
cout << &b << endl;
cout << &c << endl;
cout << &d << endl;
return 0;
}


6.2.引用的特性
引用在定义时必须初始化

一个变量可以有多个引用


引用一旦引用一个实体,再不能引用其他实体


6.3.指针 VS 引用
引用:
变量的别名,本身无独立地址,无法被重新赋值,对引用修改就是对原变量的修改
指针变量:
独立的变量,本身有独立地址,存放原变量的地址,可以被重新赋值,指针变量要修改它指向的变
量就需要进行解引用
| 引用 | 指针变量 | |
| 语法概念 | 一个变量取的别名(不开空间) | 存储一个变量的地址(要开空间) |
| 初始化 | 定义时必须初始化 | 定义是最好初始化 |
| 对象存储 | 不可以再引用其他对象 | 可以再不断改变指向对象 |
| 访问对象 | 直接访问 | 解引用访问 |
| 类型大小 | 引用类型的大小 | 地址空间所占字节个数 |
| 安全性 | 问题较少,相对安全 | 容易出现空指针与野指针问题 |
6.4.引用的使用
改变引用对象时同时改变被引用对象
避免拷贝,提高效率,语法更加简洁
引用传参
C语言中的传参只有两种:传值调用,传址调用
规则简单,无论是常数还是变量,想传就传
不需要区分左值、右值、常量、临时对象
C++中因为有引用的概念,能够给变量起别名,所以可以引用传参
这种传参方式不需要临时拷贝,提高了效率,但传参规则更加严格
#include<iostream>
using namespace std;
void Swap(int& rx, int& ry)
{
int tmp = rx;
rx = ry;
ry = tmp;
}
int main()
{
int x = 0, y = 1;
cout << x << " " << y << endl;
Swap(x, y);
cout << x << " " << y << endl;
return 0;
}

引用做返回值
正常返回时会生成临时对象,将其作为返回值
这种对象具有常性,类似const修饰,无法修改
#include<iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
}
void STPush(ST & rs, STDataType x)
{
if(rs.top == rs.capacity)
{
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType * tmp = (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++;
}
void STModifyTop(ST& rs, int x)
{
rs.a[rs.top - 1] = x;
}
int STTop(ST & rs)
{
assert(rs.top > 0);
return rs.a[rs.top];
}
int main()
{
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
STTop(st1) = 3;
return 0;
}

引用做返回值时,返回的是返回值的别名
中间没有产生临时对象,可以进行修改
示例:此时STModifyTop与STTop的效果一样
#include<iostream>
#include <assert.h>
using namespace std;
typedef int STDataType;
typedef struct Stack
{
STDataType* a;
int top;
int capacity;
}ST;
void STInit(ST& rs, int n = 4)
{
rs.a = (STDataType*)malloc(n * sizeof(STDataType));
rs.top = 0;
rs.capacity = n;
}
void STPush(ST & rs, STDataType x)
{
if(rs.top == rs.capacity)
{
printf("扩容\n");
int newcapacity = rs.capacity == 0 ? 4 : rs.capacity * 2;
STDataType * tmp = (STDataType*)realloc(rs.a, newcapacity *sizeof(STDataType));
if (tmp == NULL)
{
perror("realloc fail");
return;
}
rs.a = tmp;
rs.capacity = newcapacity;
}
rs.a[rs.top] = x;
rs.top++;
}
void STModifyTop(ST& rs, int x)
{
rs.a[rs.top - 1] = x;
}
int& STTop(ST & rs)
{
assert(rs.top > 0);
return rs.a[rs.top];
}
int main()
{
ST st1;
STInit(st1);
STPush(st1, 1);
STPush(st1, 2);
STModifyTop(st1, 4);
STTop(st1) = 4;
return 0;
}
注:
引用做返回值时,如果返回值对象为栈区的局部变量,出了作用域,函数栈帧就会被销毁,虽然返
回引用,但返回值已经销毁,此时引用就会成类似野指针的情况
示例:
变量a为栈区局部变量,func函数执行完毕后,函数栈帧销毁,变量a的内存释放
此时用悬垂引用赋值1,修改的是一块已经被销毁的空间,所以依旧打印1000


而如果返回值的对象为堆区或者全局的变量时,出了函数依旧能够找到原来对象的空间


七、const引用
7.1.权限问题
如果一个变量被const修饰,那么就必须要在它的引用前加const
对象的访问权限在引用过程中可以缩小,但是不能放大


注:b变量可读可写的,经过权限缩小后的rb只能读取
区分权限放大与赋值拷贝:

7.2.常量取别名
可以在引用前加const给常量取别名

7.3.临时对象
编译器需要一个空间暂存表达式的求值结果时,临时创建的一个未命名的对象,称为临时对象
临时对象一般指表达式右值,非引用类型的函数返回值,发生类型转换的变量
临时对象引用需要const修饰,不能进行修改

发送类型转换的变量也会产生临时对象

7.4.引用传参规则
普通引用:T&:表达式左值(有名字的变量)
常量引用:const T&:表达式左值,以及临时对象(表达式右值,常量、发生类型转换后的值)
注:常量引用的表达式左值在函数体内无法进行修改
八、inline内联
8.1.C语言宏函数局限
C语言实现宏函数会在预处理时替换展开
但宏函数实现很复杂,并且很容易出错,不方便调试
C++设计了inline代替C语言的宏函数

不能加分号

要加外括号

要加内括号

8.2.内联函数
内联函数:用inline修饰的函数
核心优势:
速度快:
省去了普通函数的所有开销(开辟栈帧、拷贝参数、跳转执行、销毁栈帧、返回数据)
直接在调用处展开内联函数并且执行函数体,效率更接近宏函数(汇编中无call指令)
更安全:
不像宏函数是纯文本替换,内联函数有严格的类型检测和作用域
避免了宏函数中运算优先级问题
使用场景:
inline适用于频繁调用的短小函数
对于递归函数,代码量大的一些函数,加上inline也会被编译器忽略
因为inline对于编译器而言只是一个建议
注:
inline不建议将声明和定义放在不同文件,因为分离会导致链接错误
因为inline被展开就没有函数地址,链接时会出现报错
一般内联函数直接放在头文件中,便于展开
vs编译器debug版本下默认不展开inline,便于调试,想展开需要手动设置


、
九、nullptr关键字
NULL实际是一个宏,在传统的C头文件(stddef.h)中,代码如下
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void*)0)
#endif
#endif
在C语言中,NULL被定义成无类型指针((void*)0)的常量
在C++中,NULL可能被定义为字面常量0
如果想通过f(NULL)调用指针版本的f(int* ptr)函数
但由于NULL被定义成了0,调用了f(int x)函数
而如果将实参改为(void*)NULL调用则会报错
只能将实参改为(int*)NULL才能够正常执行

C++11中引入了nullptr关键字
是一种特殊类型的字面量,可以转换成任意其他类型的指针类型
使用nullptr定义空指针可以避免类型转换的问题
因为nullptr只能被隐式地转换为指针类型,而不能被转换为整数类型


更多推荐


所有评论(0)