c和c++ 几个要害不同
这一小节 主要讲述一下,c言语和c++言语 几个要害的不同,体会一下c++ 相比与c言语 做了哪些改进。 有了c的基础 学习c++ 会快不少
c言语中的字符问题
c2报错,应该很简略理解了, 字符 只能和 ” 匹配 slash2 报错,由于c言语中字符串有必要是 ‘\0’ 完毕 ,而单引号显然没有 \0 这个东西,
所以你看c言语中你要写个字符串和字符是真得小心。
c++ 中有了string 今后 就便利很多了
char c1 = 'yes';
cout << c1 << endl;
string s1(1, 'yes');
cout << s1 << endl;
string s2(3, 'yes');
cout << s2 << endl;
string s3("yes");
cout << s3 << endl;
看下输出
咱们总算不必关怀究竟应该用”仍是””了。
数组退化问题
先看一下 下面的代码:
#include <iostream>
using namespace std;
double average1(int arr[5]) {
double result = 0;
int length = sizeof(arr) / sizeof(arr[0]);
cout << "average1-length:" << length << endl;
for (int i = 0; i < length; ++i) {
result += arr[i];
}
return result / length;
}
int main() {
int array[] = {1, 2, 3, 4, 5};
// 求数组长度的技巧
int length = sizeof(array) / sizeof(array[0]);
cout << "length:" << length << endl;
cout << "average1:" << average1(array) << endl;
return 0;
}
一个很简略的求平均值的程序
看下执行成果,可能不熟悉c言语的人都懵逼了, 为啥函数内部的数组的长度计算出来的值是2?
如果你用新版本的ide的话会看到提示:
讲白了,关于c言语来说, 数组作为参数来传递的时分, 实践上这个函数的参数 就变成了 数组的指针
关于64位的电脑来说,数组的指针便是巨细便是8,而一个int值巨细是4 那自然算出来便是2了。
也便是说 上面的函数等同于
double average1(int* arr) {
double result = 0;
int length = sizeof(arr) / sizeof(arr[0]);
cout << "average1-length:" << length << endl;
for (int i = 0; i < length; ++i) {
result += arr[i];
}
return result / length;
}
在C言语中,数组名常常被解说为指向数组第一个元素的指针。由于C言语中指针是一种特殊的变量类型,所以数组名也能够被视为指向一个特定类型的指针。
但是,当咱们将一个数组作为参数传递给函数时,数组名就会产生一种奇怪的“退化”现象,即它不再是指向数组的指针,而是成为了一个一般的指针。
这种现象的原因是当数组作为函数参数时,它会自动转化为指向其第一个元素的指针。因此,在函数中运用数组名时,它实践上是一个指向数组第一个元素的指针,而不是一个真正的数组。
这种“退化”现象可能会导致一些问题,特别是当咱们企图确定数组的长度时。由于在函数中,咱们只能看到指针,而不能看到指针所指向的数组的巨细。
因此,如果咱们想在函数中运用一个数组,并需求知道其巨细,咱们有必要将数组的巨细作为另一个参数传递给函数。
c言语这么做主要是为了功率考虑,毕竟如果是值传递的话 涉及到数组的复制 那功率就太低了
关于c言语来说 能够这么改(其实便是让调用者把数组的实践长度传递进去就能够了):
double average2(int* arr,int length) {
double result = 0;
cout << "average1-length:" << length << endl;
for (int i = 0; i < length; ++i) {
result += arr[i];
}
return result / length;
}
c++ 怎么处理这个问题的?
首要改一下cmake文件
set(CMAKE_CXX_STANDARD 11)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -std=c++11")
然后用stl库的方法来处理就行了
vector<int> myArray {1, 2, 3, 4, 5};
cout << "average3:" << average3(myArray) << endl;
double average3(vector<int> &v) {
double result = 0;
auto it = v.begin();
for (; it != v.end(); ++it) {
result += *it;
}
return result / v.size();
}
考虑一个问题,如何完成一个动态长度的数组?关于c言语来说,必定要用到malloc和free,很费事,而在c++中咱们很简略用vector就能够完成了 这儿就不继续写了,大家有个印象即可
算术右移和逻辑右移
先看一段代码:
int main() {
char a1=0x63;//0110 0011
a1=(a1<<4); //0011 0000
printf("0x%x \n",a1);
a1=0x63;//0110 0011
a1=(a1>>4); //0000 0110
printf("0x%x \n",a1);
char a2=0x95;//1001 0101
a2=(a2<<4); //0101 0000
printf("0x%x \n",a2);
a2=0x95;// 1001 0101
a2=(a2>>4); // 以为是0000 1001 成果实践是 1111 1001 管用右移 逻辑右移?
printf("0x%x \n",a2); //为啥是0xfffffff9 ?
return 0;
}
有问题的程序实践在这:
这个和咱们幻想中的 0000 1001的成果并不一样
实践上在c言语中
右移操作符分为算术右移和逻辑右移。
逻辑移位:左面用0填充,右边丢弃
算术移位:左面用原该值的符号位填充,右边丢弃
但常见的编译器是管用右移
要处理上面那个问题 用 无符号数来表明即可
unsigned char a2=0x95;//1001 0101
看下成果:
所以这儿c言语就有带点坑了,关于c言语来说,咱们运用位移运算符的时分 要考虑上下文的环境 究竟是管用右移动仍是逻辑右移, 究竟要不要用无符号数
c++的处理方案 bitset
int main() {
bitset<8> bt=0x95;
cout<<bt<<endl;
bt=(bt>>4);
cout<<bt<<endl;
cout<<"--------------------"<<endl;
//c++ 就简略的多,用bitset要言不烦 完成一个简略的权限判别
bitset<10> priv=0xFF;
bitset<10> P_BACKUP=(1<<6);
bitset<10> P_ADMIN=(1<<7);
cout<<priv<<endl;
cout<<P_BACKUP<<endl;
cout<<P_ADMIN<<endl;
if ((priv & P_BACKUP)==P_BACKUP){
cout<<"backup"<<endl;
}
if ((priv & P_ADMIN)==P_ADMIN){
cout<<"admin"<<endl;
}
return 0;
}
看下执行成果:
强制类型转化
看下面这个简略的代码:
int main() {
int array[] = {1, 2, 3};
cout << sizeof(array) / sizeof(array[0]) << endl;
int threshold = -1;
if (sizeof(array) / sizeof(array[0]) > threshold) {
cout << "合法数组" << endl;
} else {
cout << "不合法数组" << endl;
}
return 0;
}
应该每个人看到了 都觉得这个当地肯定会打印合法数组 对吧
但实践上跑一下你就知道
竟然是不合法的? 这是为啥?
其实是由于 sizeof 的回来值是一个无符号类型的, 当你用上述的写法的时分 threshold这个值-1 会自动转化为 有符号类型的,
cout << (unsigned int) threshold<< endl;
这个打印一下出来的值 是 4294967295,真相大白了
所以这个当地非常坑,处理方案, 把 无符号转成有符号 ,然后再比较即可
int array[] = {1, 2, 3};
cout << sizeof(array) / sizeof(array[0]) << endl;
int threshold = -1;
int res = sizeof(array) / sizeof(array[0]);
if (res > threshold) {
cout << "合法数组" << endl;
} else {
cout << "不合法数组" << endl;
}
c言语中的强制类型转化 躲藏的比较深,很简略犯错,而且这种错很难排查 ,由于你很难一眼看出来 我究竟哪里做了类型转化?
c++ 怎么处理的?
c++ 其实便是用了一些新的东西 xxx_cast 的要害字 来显现的告诉编程者 这儿做了类型转化,转化成了哪种类型
例如:
double a= static_cast<double>(1);
在c++中,处理大数的运算 一般都是用boost库,大家了解一下即可。
c言语中的字符串缺点
int main() {
char str1[] = "string";
cout << strlen(str1) << endl; //6
cout << sizeof(str1) / sizeof(str1[0]) << endl;//7
char str2[]="stri\0ng";
cout << strlen(str2) << endl; //4
cout << sizeof(str2) / sizeof(str2[0]) << endl;//8
return 0;
}
输出如下:
这个输出成果不必解说了吧,讲白了,strlen计算长度是计算的 \0 之前的长度,
char str1A[30]="stringA";
// strcat 这个函数的第一个参数的长度 必定要足够包容 拼接后的长度
strcat(str1A,str2);
cout << str1A << endl; //stringAstri
cout << strlen(str1A) << endl; //11
cout << sizeof(str1A) / sizeof(str1A[0]) << endl;//30
c++中的处理方案
string sstr1="string";
cout << sstr1 << endl; //stringAstri
cout << sstr1.length() << endl; //6
cout << sizeof(sstr1) << endl; //8
cout << sstr1.capacity() << endl; //6
cout << sstr1.size() << endl; //6
string sstr2="stri\0ng";
cout << sstr2 << endl; //stringAstri
cout << sstr2.length() << endl; //4
cout << sizeof(sstr2) << endl; //8
cout << sstr2.capacity() << endl; //4
cout << sstr2.size() << endl; //4
sstr1 += sstr2;
cout << sstr1 << endl; //stringAstri
cout << sstr1.length() << endl; //10
cout << sizeof(sstr1) << endl; //8
cout << sstr1.capacity() << endl; //12
cout << sstr1.size() << endl; //10
c++中 字符串的写法 比c言语中要便利不少, 但是要注意几点,length回来的依然是字符串实践的长度,capactity是实践容量的巨细,跟扩容相关
这儿提一下,有兴趣的能够看下redis的字符串完成,功率上比这儿原生的要高不少。
字符串的不行变性
int main() {
char array[]="helloworld";
array[2]='c';
cout<<array[2]<<endl;
char* str="helloworld";
str[2]='c'; //interrupted by signal 11: SIGSEGV)
cout<<str<<endl;
return 0;
}
上述代码中的第二段 执行起来会报错, 这儿就涉及到一个字符串的不行变性问题了,其实和java是有相似性的,大家自行体会一下即可。 不要犯错就行
C++中的指针
简略混淆的数组的指针和指针的数组
int main() {
// T* t[] 指针的数组 int* a[] 里边的值 都是指针
// T (*t)[] 数组的指针 int (*a)[] []优先级比较高
int c[3] = {1, 2, 3};
int *a[3]; // a代表一个长度为3的数组,元素是指针
int (*b)[3]; // 代表一个数组长度为3的数组的指针
b = &c; // b指向c
// 下面是将c中的每一个元素的地址赋值给a
for (int i = 0; i < 3; ++i) {
a[i] = &c[i];
}
cout << "a[0]:" << *(a[0]) <<"-------"<< a[0] << endl;
cout << (*b)[0] <<"------------"<< &((*b)[0]) << endl;
return 0;
}
const 与指针
const 在 最左面也是一样的规矩
指向指针的指针
int main() {
int a=123;
// *操作符具有从右向左的结合性
int* b=&a;
// *(*c)
int** c=&b;
return 0;
}
野指针
这儿要注意了,多数java程序员 刚开始写c的时分 很简略犯下面这个过错
原因就在于,很多javaer 会误以为 这儿执行的时分会报错,至少是个空指针之类的
但是并没有
这段程序编译是ok的,甚至运转起来也是ok的( 严格来说运转是否ok 要看命运)
这儿本质上界说了一个int 类型的指针, 但是你没有给这个指针赋予一个地址
在没有地址的情况下,你给他写入了一个值。
这个就很风险了,由于运转的时分 鬼才知道a的地址会被随机分配到哪里。。。
所以千万不要这么写!!!!!
cpp中是需求你手动给一个指针设置null的
a= NULL;
手动设置为null今后 运转起来就会犯错了
这儿跟java 是不同的,java你 界说一个String a,默认就指向null
**别的要注意的是 delete和free 之后的指针 必定要记得设置为null,不然很简略成为野指针 **
**没有初始化的,不必的 或许超出范围的指针 必定要设置为null **
这儿最难的便是判别一个指针的效果范围,只能靠多写 多找感觉
内存走漏
c++ 没有什么gc的概念,需求你自己手动管理堆区的内存
int main() {
int *c = new int(1000);
if (c != NULL) {
delete c;
c = NULL;
}
int* arr=new int[1000];
if (arr != NULL) {
// 新手比较简略忘记 数组也需求开释 数组的开释要加[]
delete[] arr;
arr = NULL;
}
// 其他目标的delete就不演示了
return 0;
}
智能指针 auto_ptr
这个东西在高版本的c++代码中 会被移除, 原因是 他有个坑
假设你用ap1 去指向一块内存区域b 别的一个人用ap2 也指向内存区域b 当ap2 指向内存区域b的时分,ap1 自动就指向null了。。。。
这和java的很不一样吧。
运用这货 必定要注意
int main() {
// 对int 运用 不需求你delete操作,在脱离效果域时自动开释
auto_ptr<int> p(new int(10));
cout << *p << endl;
// 对数组运用
auto_ptr<string> lan[5]={
auto_ptr<string>(new string("C++")),
auto_ptr<string>(new string("Java")),
auto_ptr<string>(new string("C#")),
auto_ptr<string>(new string("Python")),
auto_ptr<string>(new string("Ruby"))
};
auto_ptr<string> pC;
// 这儿就有坑了,你不能这样做,由于你的pC指向的是lan[1]的内存,而lan[1]的内存是被开释的
pC = lan[1];
// 这儿是ok的 会打印java
cout << *pC << endl;
for (int i = 0; i < 5; ++i) {
// 但是这儿就坑了,当i=1的时分 程序就会报错了
cout << *lan[i] << endl;
}
return 0;
}
智能指针 unique_ptr
他只能被一个目标持有,不支撑复制和赋值
尽管不能复制,但是能够支撑 搬运所有权。
下面看个比如
这儿显然就会报错了:
搬运所有权便是能够的:
int main() {
auto w = make_unique<int>(10);
cout << *(w.get()) << endl;
auto w2 =std::move(w);
cout << *(w2.get()) << endl;
cout << *(w.get()) << endl; // 这行会报错,由于w现已被移动了
return 0;
}
智能指针 shared_ptr
这个跟java中就有一点点像了
是经过引证计数共享一个目标
当引证计数为0时,这个目标没有被运用,就能够进行析构了。 是不是很像java中的引证计数法?
一起呢,循环引证也会在这儿呈现了。这儿的概念 javaer 应该都很熟悉了
一起,还有一个weak_ptr 与这个shared_ptr 共同工作。 也很像java中的弱引证
来看几个比如
int main() {
{
auto wA = shared_ptr<int>(new int(20));
{
auto wA2 = wA;
cout << *wA2.get() << endl;//20
cout << *wA.get() << endl;//20
cout << wA2.use_count() << endl;//2
cout << wA.use_count() << endl;//2
}
// 下面这个输出1 是由于 wA2 现已出了大括号了,效果域完毕了,所以 wA 的引证计数为1
cout << wA.use_count() << endl;//1
}
return 0;
}
再看一个比如
#include <iostream>
#include <memory>
using namespace std;
// 声明
struct B;
struct A {
shared_ptr<B> pb;
// 析构函数, 用来阐明被开释掉
~A() {
cout << "A::~A()" << endl;
}
};
struct B {
shared_ptr<A> pa;
~B() {
cout << "B::~B()" << endl;
}
};
struct BW;
struct AW {
shared_ptr<BW> pb;
// 析构函数, 用来阐明被开释掉
~AW() {
cout << "AW::~AW()" << endl;
}
};
struct BW {
weak_ptr<AW> pa;
~BW() {
cout << "BW::~BW()" << endl;
}
};
void Test() {
shared_ptr<A> tA(new A());
shared_ptr<B> tB(new B());
cout << "tA.use_count():" << tA.use_count() << endl;
cout << "tB.use_count():" << tB.use_count() << endl;
tA->pb = tB;
tB->pa = tA;
cout << "tA.use_count():" << tA.use_count() << endl;
cout << "tB.use_count():" << tB.use_count() << endl;
}
void Test2() {
shared_ptr<AW> tAw(new AW());
shared_ptr<BW> tBw(new BW());
cout << "tAw.use_count():" << tAw.use_count() << endl;
cout << "tBw.use_count():" << tBw.use_count() << endl;
tAw->pb = tBw;
tBw->pa = tAw;
cout << "tAw.use_count():" << tAw.use_count() << endl;
cout << "tBw.use_count():" << tBw.use_count() << endl;
}
int main() {
Test();
Test2();
return 0;
}
看下输出:
这儿要注意2点,
tAw的 引证计数是1的原因是 tbW是以weak的形式去持有他的
第二个要注意的便是 Test函数产生了内存走漏 由于没有走 析构函数
引证
引证在c++中便是一个 不允许修改的指针
对我这种新手来说,能用引证就不要用指针了,指针稍微不注意 就简略埋坑
引证能够认为是指定变量的别号,运用时能够认为是变量本身
引证的运用比较简略 这儿就不展开讲了。