面向对象程序设计的优点:
- 易维护
- 易扩展
- 模块化:通过设置访问级别,限制别人对自己的访问,保护了数据安全
int main(){ return 0;}
返回值0在windows下编程一般没用,但是在linux中编程,返回值有时有用
汇编与编译
生成目标文件的过程叫“
汇编
”源语言是汇编语言,目标语言是机器语言,这样的一个翻译程序称为汇编程序。
也就是说:汇编器类似编译器,只不过输入是编译程序输出是机器语言(二进制文件)
一:命名空间
命名空间用于解决相同名称的函数、类、变量等问题。本质上,命名空间就是定义了一个范围。
project2.cpp
#include<iostream>
namespace lisi {void fun1() {std::cout << "lisi::fun1()" << std::endl;}void fun2() {std::cout << "lisi::fun2()" << std::endl;}
} // 命名空间不要加“;”
project1.cpp
#include<iostream>
//声明李四的命名空间,可以作为头文件
namespace lisi {//只定义函数声明void fun1();void fun2();
}
//张三命名空间
namespace zhangsan {void fun1() {std::cout << "zhangsan::fun1()" << std::endl;}void fun2() {std::cout << "zhangsan::fun2()" << std::endl;}
}
//可以在本函数内为李四命名空间定义新函数
namespace lisi {void fun3() {std::cout << "lisi::fun3()" << std::endl;}}
int main() {zhangsan::fun1();zhangsan::fun2();lisi::fun1();lisi::fun2();lisi::fun3();return 0;
}
-
namespace{} 注意不要加
;
-
访问时候需要用
::
去访问 -
可以用using namespace __去简化访问
#include<iostream> namespace lisi {//只定义函数声明void fun1();void fun2(); } //用using简化访问 using namespace lisi;namespace zhangsan {void fun1() {std::cout << "zhangsan::fun1()" << std::endl;}void fun2() {std::cout << "zhangsan::fun2()" << std::endl;} } int main() {fun1();fun2();return 0; }
命名空间可以嵌套,您可以在一个命名空间中定义另一个命名空间,如下所示:
namespace namespace_name1 {// 代码声明namespace namespace_name2 {// 代码声明}
}
访问时需要::中嵌套::
//访问 namespace_name2 中的成员
using namespace namespace_name1::namespace_name2;
示例:
#include<iostream>
namespace aa{void fun() {std::cout << "aa::fun()" << std::endl;}namespace bb {void fun() {std::cout << "bb::fun2()" << std::endl;}}
}
int main() {aa::fun();aa::bb::fun();return 0;
}
二:cout和cin
输入输出缓冲区
-
输入缓冲区是在数据流输入之前存储输入数据的临时存储区域。
-
输出缓冲区是在数据流输出之前存储输出数据的临时存储区域。
输入输出缓冲区
就是为了保存这些输入输出流而临时开辟出的一块内存
。
使用缓冲区的好处:
- 当程序需要读取或写入大量数据时,使用缓冲区可以将这些数据先存储到内存中,然后再一次性地写入或读取,避免了频繁访问硬件的开销。
- 此外,缓冲区还可以优化数据的排列和格式,以便更高效地读取和写入数据。
cout函数
cout 语句可以与某些成员函数一起使用:
cout.write(char *str, int n)
:打印从str读取的前N个字符。cout. precision(int n)
:使用浮点值时,将小数精度设置为N 。
这个N是整个小数的位数
char aa[] = "hello";const char *bb = "world";cout.write(aa, 3) <<endl;//helcout.write(bb, 4) << endl;//worldouble pi = 3.1415926;cout.precision(4);//3.142cout << pi << endl;
cout中:endl
和"\n"
是一个效果
cin函数
getline()
cin.getline()
属于istream流,而getline()
属于string流,是不一样的两个函数//getline()的原型istream& getline ( istream &is , string &str , char delim );
is
参数是istream类的输入流对象,譬如cin;str
是待输入的string对象,表示把从输入流读入的字符串存放在这个字符串中。delim
表示遇到这个字符停止读入,在不设置的情况下系统默认该字符为’\n’,也就是回车换行符。
getline()
可以输入任何可见字符,包括空格和制表符。
getline不会将分隔符(\n)存入到字符串中,因此不需要再用函数去除行末的分隔符。
string a, b, c, d;getline(cin, a);getline(cin, b, '&');getline(cin, c, '*');getline(cin, d);cout << "a=:" << a<< endl;cout << "b=:" << b<< endl;cout << "c=:" << c<< endl;cout << "d=:" << d<< endl;
- getline()遇到自定义终止符后就不再继续读入了
- getline()会自动忽略之前输入流中的任何空格和换行符等字符
cin.getline()
该函数是iostream库中的一个函数。
cin.getline(char_array, size, delim)
-
char_array表示要存储输入内容的字符数组
-
size表示数组的最大长度,
若输入长度超出size,则不再接受后续的输入
输入过长,cin函数会出错
-
delim表示分隔符,即读取到delim时会停止输入。
默认的分隔符是换行符(\n)
。
cin.getline()函数输入多个字符串时必须先把缓冲区清空,否则会读入一个空字符串。
因为cin.getline()不能接受\n,下一个字符串会自动读入然后结束
char a[10];char b[10];cin.getline(a, 8);cin.clear();//用clear去除错误标志cin.ignore(1024,'\n');//去掉\n前的缓冲区数据cin.getline(b, 8);cout << a << "," << b << endl;
循环读取,以;为分隔符
string str;while (getline(cin, str, ';')){cout << str << endl;}
int fav_no; cin >> fav_no; cin.ignore();//忽略cin留在缓冲区的\nstring name; getline(cin , name);//不需要ignore,getline()不会在缓冲区留\nchar a[10];cin >> a;cout << name << "," << fav_no << "," << a << endl;
getline与cin.getline()的区别:
cin.getline()
接收输入字符串的是数组,getline()
是string类型。cin.getline()
可以接收空格,但不能接收回车;getline()
可以接收空格和回车。cin.getline()
会在数组结尾加’\0’,getline()
不会。
ignore()
**cin.ignore()
**它会忽略或清除输入缓冲区中的一个或多个字符。
//函数原型
istream &ignore( streamsize num=1, int delim=EOF )
//忽略num个字符或者忽略到delim就开始读入
char a[10];cin.ignore(7, 'a');cin.getline(a, 9);
- 输入:helloworld 输出:rld
- 输入:hellawolds 输出:wolds
- 输入:hellowoald 输出:ald
int a, b, c;cin >> a;cin.ignore();cin >> b;cin.ignore();cin >> c;cout << a << "," << b << "," << c << endl;
可以换行读取了
99
199
299
99,199,299
请按任意键继续. .
为自己的类对象重载:<<与>>
class book{
public:book(string a="",double p=0):name(a),price(p){}//定义输出<<friend ostream& operator<<(ostream &os, const book& bk);//定义输入>>friend istream &operator>>(istream &is, book& bk);
private:string name;double price;
};ostream& operator<<(ostream &os, const book &bk){os << bk.name << "," << bk.price << endl;return os;
}
//注意往book成员写,不可以用const
istream &operator>>(istream &is, book& bk){is >> bk.name >> bk.price;return is;
}
主函数:
book aa;cin >> aa;cout << aa;
注意:
- 必须用friend,才能让运算法访问到私有成员
- 分清:
istream与>>
和ostream与<<
执行顺序:
因为 c o u t 返回对象是 o s t r e a m 的& \text{因为}cout\text{返回对象是}ostream\text{的\&} 因为cout返回对象是ostream的&
c o u t < < a < < b < < c < < e n d l ; cout<<a<<b<<c<<endl; cout<<a<<b<<c<<endl;
等价于: < = > \text{等价于:}<=> 等价于:<=>
( ( ( ( c o u t < < a ) < < b ) < < c ) < < e n d l ) \left( \left( \left( \left( cout<<a \right) <<b \right) <<c \right) <<endl \right) ((((cout<<a)<<b)<<c)<<endl)
每次执行完还是 o s t r e a m 对象,所以可以连续调用 \text{每次执行完还是}ostream\text{对象,所以可以连续调用} 每次执行完还是ostream对象,所以可以连续调用
cin<<a<<b<<c;也同理
三:防伪式声明
头文件之间互相包含导致的重定义问题
比如一下这个例子:
- h1.h
int global_a = 100;
- h2.h
#include"h1.h" int global_b = 200;
- main.cpp
#include <iostream> #include"h1.h" #include"h2.h" using namespace std;int main() {cout << global_a << "," << global_b << endl;return 0; }
运行会出错:
redefinition of 'int global_a'
因为:h2.h包含了h1.h,所以cpp中的两个头文件h1.h和h2.h会造成重定义
解决方法:头文件加上防伪式声明
#ifndef 名字
#define 名字
#endif
h1.h
#ifndef _H1_ #define _H1_ int global_a = 100; #endif
h2,h
#ifndef _H2_ #define _H2_ #include"h1.h" int global_b = 200; #endif
四:constexpr
主要思想是通过在编译时
而不是运行时进行计算来提高程序的性能。
constexpr int fun(int x, int y) { return x * y; }
int main()
{int arr[fun(2,3)];//说明是常量return 0;
}
在编译时确定求幂
//计算pow(x,n),规定n>=0
constexpr int mypow(int x,int n){if(n==0)return 1;else{return (n % 2 == 0) ? mypow(x*x, n / 2) : x * mypow(x*x, n / 2);}
}
五:for语句
可以直接在for内放整个数组
for(auto x:{1,2,3,4,5,6}){cout << x << " ";}
using pr = pair<int, char>;vector<pr> vec{{100, 'a'},{200, 'b'},{300, 'c'}};for(auto val:vec){cout << val.first << "," << val.second << endl;}
六:c++内存
c++中内存一般分为5个区
-
栈
:一般放局部变量,由编译器负责分配和释放 -
堆
:由程序员通过new/malloc来分配,用delete/free来释放如果程序员没有释放掉,那么在程序结束后,操作系统会自动回收
-
全局/静态存储区
:放全局和静态static变量,程序结束系统释放,在C++里它们共同占用同一块内存区。 -
常量存储区
:比如字符串常量,存放的是常量不允许被修改 -
程序代码区
堆和栈
主要的区别由以下几点:
-
管理方式不同;
对于栈来讲,是由编译器自动管理,无需我们手工控制;对于堆来说,释放工作由程序员控制
-
空间大小不同;
- 栈:空间有限,分配速度快
- 堆:只要物理内存足够,操作系统也允许,就可以分配最大内存之内大小
-
能否产生碎片不同;
对于堆来讲,频繁的new/delete势必会造成内存空间的不连续,从而造成大量的碎片,使程序效率降低
栈是先进后出的,永远都不可能有一个内存块从栈中间弹出
-
生长方向不同;
-
对于堆来讲,生长方向是向上的,也就是向着内存地址增加的方向;
-
对于栈来讲,它的生长方向是向下的,是向着内存地址减小的方向增长。
-
-
分配方式不同;
堆都是动态分配的,没有静态分配的堆。栈有2种分配方式:静态分配和动态分配。
-
分配效率不同;
计算机会在底层对
栈
提供支持:分配专门的寄存器存放栈的地址,压栈出栈都有专门的指令执行这就决定了栈的效率比较高。
堆
则是C/C++函数库提供的,它的机制是很复杂的,例如为了分配一块内存,库函数会按照一定的算法在堆内存中搜索可用的足够大小的空间,如果没有足够大小的空间(可能是由于内存碎片太多),就有可能调用系统功能去增加程序数据段的内存空间,这样就有机会分到足够大小的内存,然后进行返回。显然,堆的效率比栈要低得多
malloc与free
void* malloc(size_t size);//size以字节为单位想分配的内存大小
malloc()
返回值:
- 成功----
void
指向函数分配的未初始化内存块的指针 - 失败----返回空指针
使用:
//分配一个字符char *ch = NULL;ch = (char *)malloc(sizeof(char));if(ch){*ch = 'a';cout << *ch << endl;free(ch);}//分配五个整形int *p = NULL;p = (int *)malloc(5*sizeof(int));if(!p){cout << "fail" << endl;}for (int i = 0; i < 5;i++){p[i] = i * 2;}for (int i = 0; i < 5;i++){cout << *(p + i) << " ";}free(p);
char *str = NULL;str = (char *)malloc(100*sizeof(char));if(!str){cout << "fail" << endl;}strcpy_s(str, 100,"hello,world");//超出会警告,更安全cout << str << endl;free(str);
注意
strcpy_s
(地址,长度,拷贝字符串),超出长度会警告,比strcpy
更安全
new与delete
注意:c++只用new和delete,不再用C语言的malloc和free
三种用法:
- 指针变量名 =
new
类型; - 指针变量名 =
new
类型(初始值)
;//给出初始值 - 指针变量名 =
new
类型[内存单元个数]
;//分配数组
类型* 指针=new 类型 [0];
是合法的
使用1:
int* myInt = new int;//new int [1]if(myInt){*myInt = 8;cout << *myInt << endl;delete myInt;}
注意new和delete都是c++的标识符
当**
new
**用于为C++类对象分配内存时,分配内存后会调用该对象的构造函数。
使用
delete
运算符来释放由运算符分配的内存**new
**。使用**
delete[]
运算符删除由运算符分配的数组new
**。
带初值
int* p=new int[5] ()
;//初始化5个0
string* mm=new string[3]
();//初始化3个空字符串
char* myInt = new char('a');//初始值if(!myInt){cout << "fails\n";}cout << *myInt << endl;delete myInt;
string *str = new string[5]{"hello", "world", "aa", "vv", "jj"};for (int i = 0; i < 5;i++){cout << str[i] << endl;}
注意分配多个内存用delete[]来释放
int *p = new int[10];if(!p)cout << "fails\n";for (int i = 0; i < 10;i++){p[i] = (rand() % 100) + 20;}for (int i = 0; i < 10;i++){cout << p[i] << " ";}delete[] p;
二维数组的分配
// 初始化m行n列数组int m = 5;int n = 4;//定义m个int*int **arr = new int *[m];//每个int*对应n个元素for (int i = 0; i < m; i++){arr[i] = new int[n];}/*初始化*/for (int i = 0; i < m; i++)for (int j = 0; j < n; j++)arr[i][j] = rand() % 50;/*打印*/for (int i = 0; i < m; i++){for (int j = 0; j < n; j++){cout << arr[i][j] << " ";}cout << endl;}
也可以传递成函数
int** gen_arr(int m,int n){int **arr = new int *[m];for (int i = 0; i < m;i++){arr[i] = new int[n];/*初始化*/for (int j = 0; j < n;j++){arr[i][j] = i * m + j * n;}}return arr;
}
数组指针—指向数组的指针(行指针)
int (*p)[n];
因为()的优先级高,所以*运算符先把p声明为一个
指针
。指向一个整型的一维数组,这个一维数组的长度是 n,也可以说是 p 的步长。
执行 p+1 时,p 要跨过 n 个整型数据的长度。
int m = 3, n = 4;int(*p)[4] = new int[m][4];/*赋值*/for (int i = 0; i < m;i++){for (int j = 0; j < 4;j++){p[i][j] = m * i + j;}}/*输出*/for (int i = 0; i < m;i++){for (int j = 0; j < n;j++){cout << p[i][j] << " ";}cout << endl;}
*(*(*(arr + i ) + j ) + k) 等价于下标表达式 arr[i][j][k]
char a[4] = {'a', 'b', 'c', 'd'};char(*p)[4] = &a;//p只有一行for (int i = 0; i < 4;i++){cout << p[0][i] << " ";}
指针数组
—元素是指针的数组
int *p[n];
- []优先级高,先与 p 结合成为一个数组
- 再由
int*
说明这是一个整型指针数组,它有 n 个指针类型的数组元素。
p[0]、p[1]、p[2]...p[n-1]是指针
注意:p这里是未知变量,想要赋值需要用*p
指针数组是多个指针变量,以数组形式存在内存当中,占有多个指针的存储空间
int a[3][4] = {{1, 2, 3, 7},{4, 5, 6, 2},{9, 5, 7, 3}};int *p[3]; // 行for (int i = 0; i < 3; i++){p[i] = a[i]; //*(p+i)}for (int i = 0; i < 3; i++){for (int j = 0; j < 4; j++){cout << p[i][j] << " ";}cout << endl;}
数组指针 vs 指针数组
指针数组
它是“储存指针的数组”的简称
首先它是一个数组,数组的元素都是指针,数组占多少个字节由数组本身的大小决定,每一个元素都是一个指针。
数组指针
它是“指向数组的指针”的简称
首先它是一个指针,它指向一个数组。在 32 位系统下任何类型的指针永远是占 4 个字节,至于它指向的数组占多少字节,不知道,具体要看数组大小。
七:nullptr
nullptr只能给指针变量赋初值
int a = NULL;//0int b = nullptr;//出错int *c = NULL;int *d = nullptr;
nullptr和NULL的类型不同
cout << typeid(NULL).name() << endl;//intcout << typeid(nullptr).name() << endl;//std::nullptr_t